diff --git a/.codecov.yml b/.codecov.yml index 494bafc0..48641086 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,7 @@ ignore: - "**/*.pb.go" # ignore protoc generated files - "**/*.connect.go" # ignore connect-go generated files + - "graph/generated.go" # ignore gqlgen generated files for introspection coverage: range: "70...85" status: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68dc97c8..79eef6af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,22 +16,24 @@ jobs: uses: actions/setup-go@v5 with: go-version-file: go.mod + cache-dependency-path: "**/*.sum" - name: Set up node.js uses: actions/setup-node@v4 with: node-version: 23 - - name: Set up buf - uses: bufbuild/buf-setup-action@v1.48.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up tools + run: | + go install github.com/mfridman/tparse + go install github.com/sqlc-dev/sqlc/cmd/sqlc + go install github.com/99designs/gqlgen + go install golang.org/x/tools/cmd/stringer + working-directory: ./tools - name: Build Backend run: | - go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest go generate ./... go build -v ./... - name: Test Backend run: | - go install github.com/mfridman/tparse@latest go test -v -coverprofile=coverage.cov -coverpkg ./... -covermode=atomic ./... -json | tee output.json | tparse -follow || true tparse -format markdown -file output.json > $GITHUB_STEP_SUMMARY - name: Upload coverage reports to Codecov diff --git a/buf.gen.yaml b/buf.gen.yaml deleted file mode 100644 index bccd7b2e..00000000 --- a/buf.gen.yaml +++ /dev/null @@ -1,10 +0,0 @@ -version: v1 -plugins: - - plugin: buf.build/connectrpc/go - out: gen - opt: - - paths=source_relative - - plugin: buf.build/protocolbuffers/go - out: gen - opt: - - paths=source_relative diff --git a/buf.lock b/buf.lock deleted file mode 100644 index 2b3f954f..00000000 --- a/buf.lock +++ /dev/null @@ -1,8 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v1 -deps: - - remote: buf.build - owner: googleapis - repository: googleapis - commit: 8bc2c51e08c447cd8886cdea48a73e14 - digest: shake256:a969155953a5cedc5b2df5b42c368f2bc66ff8ce1804bc96e0f14ff2ee8a893687963058909df844d1643cdbc98ff099d2daa6bc9f9f5b8886c49afdc60e19af diff --git a/buf.openapi.gen.yaml b/buf.openapi.gen.yaml deleted file mode 100644 index 462cfbb5..00000000 --- a/buf.openapi.gen.yaml +++ /dev/null @@ -1,6 +0,0 @@ -version: v1 -plugins: - - plugin: buf.build/community/google-gnostic-openapi - out: . - opt: - - enum_type=string diff --git a/buf.yaml b/buf.yaml deleted file mode 100644 index a042d9b0..00000000 --- a/buf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -version: v1 -name: buf.build/clouditor/api -deps: - - buf.build/googleapis/googleapis -breaking: - use: - - FILE -lint: - use: - - DEFAULT diff --git a/cli/cli.go b/cli/cli.go index 768582d4..88553e13 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -21,14 +21,12 @@ import ( "context" "encoding/json" "fmt" - "log/slog" + "io" "net/http" "os" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" + "github.com/shurcooL/graphql" - "connectrpc.com/connect" - "github.com/lmittmann/tint" oauth2 "github.com/oxisto/oauth2go" "github.com/urfave/cli/v3" ) @@ -40,8 +38,7 @@ var SessionKey sessionKeyType // Session holds all necessary information about the current CLI session. type Session struct { - PortfolioClient portfoliov1connect.PortfolioServiceClient `json:"-"` - SecuritiesClient portfoliov1connect.SecuritiesServiceClient `json:"-"` + GraphQL *graphql.Client opts *SessionOptions } @@ -78,6 +75,9 @@ func (opts *SessionOptions) MergeWith(other *SessionOptions) *SessionOptions { // DefaultBaseURL is the default base URL for all services. const DefaultBaseURL = "http://localhost:8080" +// DefaultGraphURL is the default URL for the GraphQL service. +const DefaultGraphURL = "http://localhost:9090/graphql" + // NewSession creates a new session. func NewSession(opts *SessionOptions) (s *Session) { def := &SessionOptions{ @@ -138,38 +138,17 @@ func (s *Session) Save() (err error) { // initClients initializes the clients for the session. func (s *Session) initClients() { - interceptor := func(next connect.UnaryFunc) connect.UnaryFunc { - return connect.UnaryFunc(func( - ctx context.Context, - req connect.AnyRequest, - ) (connect.AnyResponse, error) { - if req.Spec().IsClient { - var t, err = s.opts.OAuth2Config.TokenSource(context.Background(), s.opts.Token).Token() - if err != nil { - slog.Error("Could not retrieve token", tint.Err(err)) - } else { - req.Header().Set("Authorization", "Bearer "+t.AccessToken) - } - } - return next(ctx, req) - }) - } - if s.opts.HttpClient == nil { s.opts.HttpClient = http.DefaultClient } - s.PortfolioClient = portfoliov1connect.NewPortfolioServiceClient( - s.opts.HttpClient, s.opts.BaseURL, - connect.WithHTTPGet(), - connect.WithInterceptors(connect.UnaryInterceptorFunc(interceptor)), - ) + s.GraphQL = graphql.NewClient(s.opts.BaseURL+"/graphql/query", s.opts.HttpClient) +} - s.SecuritiesClient = portfoliov1connect.NewSecuritiesServiceClient( - s.opts.HttpClient, s.opts.BaseURL, - connect.WithHTTPGet(), - connect.WithInterceptors(connect.UnaryInterceptorFunc(interceptor)), - ) +func (s *Session) WriteJSON(w io.Writer, v any) { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + enc.Encode(v) } // FromContext extracts the session from the context. diff --git a/cli/commands/account.go b/cli/commands/account.go index ad43b01e..2e06cf4e 100644 --- a/cli/commands/account.go +++ b/cli/commands/account.go @@ -18,49 +18,243 @@ package commands import ( "context" + "errors" "fmt" + "time" mcli "github.com/oxisto/money-gopher/cli" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/models" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" - "connectrpc.com/connect" + "github.com/shurcooL/graphql" "github.com/urfave/cli/v3" ) -// BankAccountCmd is the command for bank account related commands. -var BankAccountCmd = &cli.Command{ - Name: "bank-account", - Usage: "Manage bank accounts", +// AccountCmd is the command for account related commands. +var AccountCmd = &cli.Command{ + Name: "account", + Usage: "Manage accounts", Before: mcli.InjectSession, Commands: []*cli.Command{ { Name: "create", - Usage: "Creates a new bank account", - Action: CreateBankAccount, + Usage: "Creates a new account", + Action: CreateAccount, Flags: []cli.Flag{ - &cli.StringFlag{Name: "id", Usage: "The identifier of the portfolio, e.g. mybank-myportfolio", Required: true}, - &cli.StringFlag{Name: "display-name", Usage: "The display name of the portfolio"}, + &cli.StringFlag{Name: "id", Usage: "The unique ID for the account", Required: true}, + &cli.StringFlag{Name: "display-name", Usage: "The display name of the account", Required: true}, + &cli.GenericFlag{Name: "type", Usage: "The type of bank account", Value: func() *accounts.AccountType { + var typ accounts.AccountType = accounts.AccountTypeBrokerage + return &typ + }()}, + }, + }, + { + Name: "list", + Usage: "Lists all accounts", + Action: ListAccounts, + }, + { + Name: "delete", + Usage: "Deletes an account", + Action: DeleteAccount, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "id", Usage: "The unique ID for the account", Required: true}, + &cli.BoolFlag{Name: "confirm", Usage: "Confirm account deletion", Required: true}, + }, + }, + { + Name: "transactions", + Usage: "Subcommands supporting transactions within one portfolio", + Commands: []*cli.Command{ + { + Name: "list", + Usage: "Lists all transactions for an account", + Action: ListTransactions, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "account-id", Usage: "The ID of the account the transaction is coming from or is destined to", Required: true}, + }, + }, + { + Name: "create", + Usage: "Creates a transaction. Defaults to a \"buy\" transaction", + Action: CreateTransaction, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "source-account-id", Usage: "The ID of the account the transaction is coming from", Required: true}, + &cli.StringFlag{Name: "destination-account-id", Usage: "The ID of the account the transaction is destined to", Required: true}, + &cli.StringFlag{Name: "security-id", Usage: "The ID of the security this transaction belongs to (its ISIN)", Required: true}, + &cli.GenericFlag{Name: "type", Usage: "The type of the transaction", Required: true, DefaultText: "BUY", Value: func() *events.PortfolioEventType { + var typ events.PortfolioEventType = events.PortfolioEventTypeBuy + return &typ + }()}, + &cli.FloatFlag{Name: "amount", Usage: "The amount of securities involved in the transaction", Required: true}, + &cli.FloatFlag{Name: "price", Usage: "The price without fees or taxes", Required: true}, + &cli.FloatFlag{Name: "fees", Usage: "Any fees that applied to the transaction"}, + &cli.FloatFlag{Name: "taxes", Usage: "Any taxes that applied to the transaction"}, + &cli.StringFlag{Name: "time", Usage: "The time of the transaction. Defaults to 'now'", DefaultText: "now"}, + }, + }, }, }, }, } -// CreateBankAccount creates a new bank account. -func CreateBankAccount(ctx context.Context, cmd *cli.Command) error { +// CreateAccount creates a new bank account. +func CreateAccount(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - res, err := s.PortfolioClient.CreateBankAccount( - context.Background(), - connect.NewRequest(&portfoliov1.CreateBankAccountRequest{ - BankAccount: &portfoliov1.BankAccount{ - Id: cmd.String("id"), - DisplayName: cmd.String("display-name"), - }, - }), - ) + + var query struct { + CreateAccount struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + Type accounts.AccountType `json:"type"` + } `graphql:"createAccount(input: $input)" json:"account"` + } + + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "input": models.AccountInput{ + ID: cmd.String("id"), + DisplayName: cmd.String("display-name"), + Type: *cmd.Generic("type").(*accounts.AccountType), + }, + }) if err != nil { return err } - fmt.Fprint(cmd.Writer, res.Msg) + s.WriteJSON(cmd.Writer, query.CreateAccount) + + return nil +} + +// ListAccounts lists all accounts. +func ListAccounts(ctx context.Context, cmd *cli.Command) (err error) { + s := mcli.FromContext(ctx) + + var query struct { + Accounts []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + Type accounts.AccountType `json:"type"` + } `json:"accounts"` + } + + err = s.GraphQL.Query(context.Background(), &query, nil) + if err != nil { + return err + } + + s.WriteJSON(cmd.Writer, query) + + return nil +} + +// DeleteAccount deletes an account. +func DeleteAccount(ctx context.Context, cmd *cli.Command) (err error) { + s := mcli.FromContext(ctx) + + // Confirm deletion + if !cmd.Bool("confirm") { + return errors.New("please confirm delete with --confirm") + } + + var query struct { + DeleteAccount struct { + ID string `json:"id"` + } `graphql:"deleteAccount(id: $id)" json:"account"` + } + + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "id": graphql.String(cmd.String("id")), + }) + if err != nil { + return err + } + + fmt.Fprintf(cmd.Writer, "Account %q deleted.\n", query.DeleteAccount.ID) + + return nil +} + +// ListTransactions lists all transactions for an account. +func ListTransactions(ctx context.Context, cmd *cli.Command) (err error) { + s := mcli.FromContext(ctx) + + var query struct { + Transactions []struct { + ID string `json:"id"` + Time time.Time `json:"time"` + Type events.PortfolioEventType `json:"type"` + } `graphql:"transactions(accountID: $accountID)" json:"transactions"` + } + + err = s.GraphQL.Query(context.Background(), &query, map[string]interface{}{ + "accountID": graphql.String(cmd.String("account-id")), + }) + if err != nil { + return err + } + + s.WriteJSON(cmd.Writer, query) + + return nil +} + +// CreateTransaction creates a transaction. +func CreateTransaction(ctx context.Context, cmd *cli.Command) (err error) { + s := mcli.FromContext(ctx) + + var query struct { + CreateTransaction struct { + ID string `json:"id"` + Time string `json:"time"` + } `graphql:"createTransaction(input: $input)" json:"account"` + } + + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "input": models.TransactionInput{ + Time: time.Now(), + SourceAccountID: cmd.String("source-account-id"), + DestinationAccountID: cmd.String("destination-account-id"), + Type: *cmd.Generic("type").(*events.PortfolioEventType), + SecurityID: cmd.String("security-id"), + Price: &models.CurrencyInput{Amount: int(cmd.Float("price") * 100), Symbol: "EUR"}, + Fees: &models.CurrencyInput{Amount: int(cmd.Float("fees") * 100), Symbol: "EUR"}, + Taxes: &models.CurrencyInput{Amount: int(cmd.Float("taxes") * 100), Symbol: "EUR"}, + Amount: cmd.Float("amount"), + }, + }) + if err != nil { + return err + } + /* + + s := mcli.FromContext(ctx) + var req = connect.NewRequest(&portfoliov1.CreatePortfolioTransactionRequest{ + Transaction: &portfoliov1.PortfolioEvent{ + PortfolioId: cmd.String("portfolio-id"), + SecurityId: cmd.String("security-id"), + Type: eventTypeFrom(cmd.String("type")), + Amount: cmd.Float("amount"), + Time: timeOrNow(cmd.Timestamp("time")), + Price: portfoliov1.Value(int32(cmd.Float("price") * 100)), + Fees: portfoliov1.Value(int32(cmd.Float("fees") * 100)), + Taxes: portfoliov1.Value(int32(cmd.Float("taxes") * 100)), + }, + }) + + res, err := s.PortfolioClient.CreatePortfolioTransaction(context.Background(), req) + if err != nil { + return err + } + + fmt.Printf("Successfully created a %s transaction (%s) for security %s in %s.\n", + color.CyanString(cmd.String("type")), + color.GreenString(res.Msg.Id), + color.CyanString(res.Msg.SecurityId), + color.CyanString(res.Msg.PortfolioId), + )*/ + return nil } diff --git a/cli/commands/account_test.go b/cli/commands/account_test.go new file mode 100644 index 00000000..5089b5da --- /dev/null +++ b/cli/commands/account_test.go @@ -0,0 +1,251 @@ +// Copyright 2023 Christian Banse +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file is part of The Money Gopher. + +package commands + +import ( + "context" + "testing" + "time" + + "github.com/oxisto/assert" + "github.com/oxisto/money-gopher/internal" + "github.com/oxisto/money-gopher/internal/testdata" + "github.com/oxisto/money-gopher/internal/testing/clitest" + "github.com/oxisto/money-gopher/internal/testing/servertest" + "github.com/oxisto/money-gopher/persistence" + "github.com/urfave/cli/v3" +) + +func TestCreateAccount(t *testing.T) { + srv := servertest.NewServer(internal.NewTestDB(t)) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, + AccountCmd.Command("create").Flags, + "--id", "myaccount", + "--display-name", "My Account", + "--type", "BANK", + ), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := CreateAccount(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("CreateAccount() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestListAccounts(t *testing.T) { + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + })) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + wantRec assert.Want[*clitest.CommandRecorder] + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, AccountCmd.Command("list").Flags), + }, + wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { + return assert.Equals(t, `{ + "accounts": [ + { + "id": "myaccount", + "displayName": "My Account", + "type": "BANK" + } + ] +} +`, rec.String()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := clitest.Record(tt.args.cmd) + + if err := ListAccounts(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("ListAccounts() error = %v, wantErr %v", err, tt.wantErr) + } + + tt.wantRec(t, rec) + }) + } +} + +func TestDeleteAccount(t *testing.T) { + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + })) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + wantRec assert.Want[*clitest.CommandRecorder] + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, AccountCmd.Command("delete").Flags, "--id", "myaccount", "--confirm"), + }, + wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { + return assert.Equals(t, "Account \"myaccount\" deleted.\n", rec.String()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := clitest.Record(tt.args.cmd) + + if err := DeleteAccount(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("DeleteAccount() error = %v, wantErr %v", err, tt.wantErr) + } + + tt.wantRec(t, rec) + }) + } +} + +func TestListTransactions(t *testing.T) { + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateBuyTransactionParams) + assert.NoError(t, err) + })) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, AccountCmd.Command("transactions").Command("list").Flags, "--account-id", testdata.TestBrokerageAccount.ID), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ListTransactions(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("ListTransactions() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCreateTransaction(t *testing.T) { + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) + assert.NoError(t, err) + })) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, + AccountCmd.Command("transactions").Command("create").Flags, + "--source-account-id", testdata.TestBankAccount.ID, + "--destination-account-id", testdata.TestBrokerageAccount.ID, + "--security-id", testdata.TestSecurity.ID, + "--type", "BUY", + "--amount", "10", + "--price", "10", + "--fees", "0", + "--taxes", "0", + "--time", time.Now().Format(time.RFC3339), + ), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := CreateTransaction(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("CreateTransaction() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cli/commands/init.go b/cli/commands/init.go index 429d43ab..5128898c 100644 --- a/cli/commands/init.go +++ b/cli/commands/init.go @@ -31,7 +31,7 @@ var CLICmd = &cli.Command{ Commands: []*cli.Command{ PortfolioCmd, SecuritiesCmd, - BankAccountCmd, + AccountCmd, LoginCmd, }, } diff --git a/cli/commands/portfolio.go b/cli/commands/portfolio.go index 1fc0ba7c..8005131f 100644 --- a/cli/commands/portfolio.go +++ b/cli/commands/portfolio.go @@ -19,18 +19,15 @@ package commands import ( "context" "fmt" - "io" - "os" "strings" "time" mcli "github.com/oxisto/money-gopher/cli" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/models" - "connectrpc.com/connect" "github.com/fatih/color" "github.com/urfave/cli/v3" - "google.golang.org/protobuf/types/known/timestamppb" ) // PortfolioCmd is the command for portfolio related commands. @@ -46,6 +43,7 @@ var PortfolioCmd = &cli.Command{ Flags: []cli.Flag{ &cli.StringFlag{Name: "id", Usage: "The identifier of the portfolio, e.g. mybank-myportfolio", Required: true}, &cli.StringFlag{Name: "display-name", Usage: "The display name of the portfolio"}, + &cli.StringSliceFlag{Name: "account-ids", Usage: "The account IDs that should be linked to the portfolio"}, }, }, { @@ -66,21 +64,6 @@ var PortfolioCmd = &cli.Command{ Name: "transactions", Usage: "Subcommands supporting transactions within one portfolio", Commands: []*cli.Command{ - { - Name: "create", - Usage: "Creates a transaction. Defaults to a \"buy\" transaction", - Action: CreateTransaction, - Flags: []cli.Flag{ - &cli.StringFlag{Name: "portfolio-id", Usage: "The name of the portfolio where the transaction will be created in", Required: true}, - &cli.StringFlag{Name: "security-id", Usage: "The ID of the security this transaction belongs to (its ISIN)", Required: true}, - &cli.StringFlag{Name: "type", Usage: "The type of the transaction", Required: true, DefaultText: "buy"}, - &cli.FloatFlag{Name: "amount", Usage: "The amount of securities involved in the transaction", Required: true}, - &cli.FloatFlag{Name: "price", Usage: "The price without fees or taxes", Required: true}, - &cli.FloatFlag{Name: "fees", Usage: "Any fees that applied to the transaction"}, - &cli.FloatFlag{Name: "taxes", Usage: "Any taxes that applied to the transaction"}, - &cli.StringFlag{Name: "time", Usage: "The time of the transaction. Defaults to 'now'", DefaultText: "now"}, - }, - }, { Name: "import", Usage: "Imports transactions from CSV", @@ -96,75 +79,93 @@ var PortfolioCmd = &cli.Command{ } // ListPortfolio lists all portfolios. -func ListPortfolio(ctx context.Context, cmd *cli.Command) error { +func ListPortfolio(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - res, err := s.PortfolioClient.ListPortfolios( - context.Background(), - connect.NewRequest(&portfoliov1.ListPortfoliosRequest{}), - ) + + var query struct { + Portfolios []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + //Snapshot models.PortfolioSnapshot `graphql:"snapshot(when: $when)" json:"snapshot"` + Snapshot struct { + TotalMarketValue currency.Currency `json:"totalMarketValue"` + TotalProfitOrLoss currency.Currency `json:"totalProfitOrLoss"` + TotalGains float64 `json:"totalGains"` + } `graphql:"snapshot(when: $when)" json:"snapshot"` + } `json:"portfolios"` + } + + err = s.GraphQL.Query(context.Background(), &query, map[string]any{ + "when": time.Now(), + }) if err != nil { return err - } else { - in := `This is a list of all portfolios. -` - - for _, portfolio := range res.Msg.Portfolios { - snapshot, _ := s.PortfolioClient.GetPortfolioSnapshot( - context.Background(), - connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: portfolio.Id, - }), - ) - - in += fmt.Sprintf(` -| %-*s | -| %s | %s | -| %-*s | %*s | -| %-*s | %*s | -`, - 15+15+3, color.New(color.FgWhite, color.Bold).Sprint(portfolio.DisplayName), - strings.Repeat("-", 15), - strings.Repeat("-", 15), - 15, "Market Value", - 15, snapshot.Msg.TotalMarketValue.Pretty(), - 15, "Performance", - 15, fmt.Sprintf("%s € (%s %%)", - greenOrRed(float64(snapshot.Msg.TotalProfitOrLoss.Value/100)), - greenOrRed(snapshot.Msg.TotalGains*100), - ), - ) - } - - //out, _ := glamour.Render(in, "dark") - fmt.Println(in) } + var in string + + for _, portfolio := range query.Portfolios { + snapshot := portfolio.Snapshot + + in += fmt.Sprintf(` + | %-*s | + | %s | %s | + | %-*s | %*s | + | %-*s | %*s | + `, + 15+15+3, color.New(color.FgWhite, color.Bold).Sprint(portfolio.DisplayName), + strings.Repeat("-", 15), + strings.Repeat("-", 15), + 15, "Market Value", + 15, snapshot.TotalMarketValue.Pretty(), + 15, "Performance", + 15, fmt.Sprintf("%s € (%s %%)", + greenOrRed(float64(snapshot.TotalProfitOrLoss.Amount/100)), + greenOrRed(snapshot.TotalGains*100), + ), + ) + } + + fmt.Fprintln(cmd.Writer, in) + return nil } // CreatePortfolio creates a new portfolio. -func CreatePortfolio(ctx context.Context, cmd *cli.Command) error { +func CreatePortfolio(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - res, err := s.PortfolioClient.CreatePortfolio( - context.Background(), - connect.NewRequest(&portfoliov1.CreatePortfolioRequest{ - Portfolio: &portfoliov1.Portfolio{ - Id: cmd.String("id"), - DisplayName: cmd.String("display-name"), - }, - }), - ) + + var query struct { + CreatePortfolio struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + Accounts []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + Type string `json:"type"` + } `json:"accounts"` + } `graphql:"createPortfolio(input: $input)" json:"account"` + } + + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "input": models.PortfolioInput{ + ID: cmd.String("id"), + DisplayName: cmd.String("display-name"), + AccountIds: cmd.StringSlice("account-ids"), + }, + }) if err != nil { return err } - fmt.Println(res.Msg) + s.WriteJSON(cmd.Writer, query.CreatePortfolio) + return nil } // ShowPortfolio shows details about a portfolio. func ShowPortfolio(ctx context.Context, cmd *cli.Command) error { - s := mcli.FromContext(ctx) + /*s := mcli.FromContext(ctx) res, err := s.PortfolioClient.GetPortfolioSnapshot( context.Background(), connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ @@ -176,7 +177,7 @@ func ShowPortfolio(ctx context.Context, cmd *cli.Command) error { return err } - fmt.Println(res.Msg) + fmt.Println(res.Msg)*/ return nil } @@ -188,37 +189,7 @@ func greenOrRed(f float64) string { } } -// CreateTransaction creates a transaction. -func CreateTransaction(ctx context.Context, cmd *cli.Command) error { - s := mcli.FromContext(ctx) - var req = connect.NewRequest(&portfoliov1.CreatePortfolioTransactionRequest{ - Transaction: &portfoliov1.PortfolioEvent{ - PortfolioId: cmd.String("portfolio-id"), - SecurityId: cmd.String("security-id"), - Type: eventTypeFrom(cmd.String("type")), - Amount: cmd.Float("amount"), - Time: timeOrNow(cmd.Timestamp("time")), - Price: portfoliov1.Value(int32(cmd.Float("price") * 100)), - Fees: portfoliov1.Value(int32(cmd.Float("fees") * 100)), - Taxes: portfoliov1.Value(int32(cmd.Float("taxes") * 100)), - }, - }) - - res, err := s.PortfolioClient.CreatePortfolioTransaction(context.Background(), req) - if err != nil { - return err - } - - fmt.Printf("Successfully created a %s transaction (%s) for security %s in %s.\n", - color.CyanString(cmd.String("type")), - color.GreenString(res.Msg.Id), - color.CyanString(res.Msg.SecurityId), - color.CyanString(res.Msg.PortfolioId), - ) - - return nil -} - +/* func eventTypeFrom(typ string) portfoliov1.PortfolioEventType { if typ == "buy" { return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY @@ -239,11 +210,11 @@ func timeOrNow(t time.Time) *timestamppb.Timestamp { } return timestamppb.New(t) -} +}*/ // ImportTransactions imports transactions from a CSV file func ImportTransactions(ctx context.Context, cmd *cli.Command) error { - s := mcli.FromContext(ctx) + /*s := mcli.FromContext(ctx) // Read from args[1] f, err := os.Open(cmd.String("csv-file")) if err != nil { @@ -267,22 +238,27 @@ func ImportTransactions(ctx context.Context, cmd *cli.Command) error { return err } - fmt.Println(res.Msg) + fmt.Println(res.Msg)*/ return nil } // PredictPortfolios predicts the portfolios for shell completion. func PredictPortfolios(ctx context.Context, cmd *cli.Command) { s := mcli.FromContext(ctx) - res, err := s.PortfolioClient.ListPortfolios( - context.Background(), - connect.NewRequest(&portfoliov1.ListPortfoliosRequest{}), - ) + + var query struct { + Portfolios []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + } `json:"portfolios"` + } + + err := s.GraphQL.Query(context.Background(), &query, nil) if err != nil { return } - for _, p := range res.Msg.Portfolios { - fmt.Fprintf(cmd.Root().Writer, "%s:%s\n", p.Id, p.DisplayName) + for _, p := range query.Portfolios { + fmt.Fprintf(cmd.Writer, "%s:%s\n", p.ID, p.DisplayName) } } diff --git a/cli/commands/portfolio_test.go b/cli/commands/portfolio_test.go index ff822ea0..4d17e3c2 100644 --- a/cli/commands/portfolio_test.go +++ b/cli/commands/portfolio_test.go @@ -20,15 +20,36 @@ import ( "context" "testing" - "github.com/oxisto/assert" "github.com/oxisto/money-gopher/internal" + "github.com/oxisto/money-gopher/internal/testdata" "github.com/oxisto/money-gopher/internal/testing/clitest" "github.com/oxisto/money-gopher/internal/testing/servertest" + "github.com/oxisto/money-gopher/persistence" + + "github.com/oxisto/assert" "github.com/urfave/cli/v3" ) func TestListPortfolio(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t)) + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreatePortfolio(context.Background(), testdata.TestCreatePortfolioParams) + assert.NoError(t, err) + + err = db.Queries.AddAccountToPortfolio(context.Background(), testdata.TestAddAccountToPortfolioParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateDepositTransactionParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateBuyTransactionParams) + assert.NoError(t, err) + })) defer srv.Close() type args struct { @@ -44,7 +65,9 @@ func TestListPortfolio(t *testing.T) { name: "happy path", args: args{ ctx: clitest.NewSessionContext(t, srv), - cmd: &cli.Command{}, + cmd: clitest.MockCommand(t, + PortfolioCmd.Command("list").Flags, + ), }, }, } @@ -59,7 +82,10 @@ func TestListPortfolio(t *testing.T) { } func TestCreatePortfolio(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t)) + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + })) defer srv.Close() type args struct { @@ -75,7 +101,12 @@ func TestCreatePortfolio(t *testing.T) { name: "happy path", args: args{ ctx: clitest.NewSessionContext(t, srv), - cmd: &cli.Command{}, + cmd: clitest.MockCommand(t, + PortfolioCmd.Command("create").Flags, + "--id", "mynewportfolio", + "--display-name", "My New Portfolio", + "--account-ids", testdata.TestCreateBankAccountParams.ID, + ), }, }, } @@ -130,47 +161,6 @@ func TestShowPortfolio(t *testing.T) { } } -func TestCreateTransaction(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t)) - defer srv.Close() - - type args struct { - ctx context.Context - cmd *cli.Command - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "happy path", - args: args{ - ctx: clitest.NewSessionContext(t, srv), - cmd: clitest.MockCommand(t, - PortfolioCmd.Commands[3].Commands[0].Flags, - "--portfolio-id", "myportfolio", - "--security-id", "mysecurity", - "--type", "buy", - "--amount", "10", - "--price", "10", - "--fees", "0", - "--taxes", "0", - "--time", "2023-01-01", - ), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := CreateTransaction(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { - t.Errorf("CreateTransaction() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - func TestImportTransactions(t *testing.T) { srv := servertest.NewServer(internal.NewTestDB(t)) defer srv.Close() @@ -207,7 +197,16 @@ func TestImportTransactions(t *testing.T) { } func TestPredictPortfolios(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t)) + srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreatePortfolio(context.Background(), persistence.CreatePortfolioParams{ + ID: "mybank/myportfolio", + DisplayName: "My Portfolio", + }) + assert.NoError(t, err) + })) defer srv.Close() type args struct { @@ -226,7 +225,7 @@ func TestPredictPortfolios(t *testing.T) { cmd: &cli.Command{}, }, wantRec: func(t *testing.T, r *clitest.CommandRecorder) bool { - return assert.Equals(t, "mybank-myportfolio:My Portfolio\n", r.String()) + return assert.Equals(t, "mybank/myportfolio:My Portfolio\n", r.String()) }, }, } diff --git a/cli/commands/securities.go b/cli/commands/securities.go index d189eeeb..c36b8694 100644 --- a/cli/commands/securities.go +++ b/cli/commands/securities.go @@ -22,9 +22,9 @@ import ( "fmt" mcli "github.com/oxisto/money-gopher/cli" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" - "connectrpc.com/connect" + "github.com/shurcooL/graphql" "github.com/urfave/cli/v3" ) @@ -39,6 +39,14 @@ var SecuritiesCmd = &cli.Command{ Usage: "Lists all securities", Action: ListSecurities, }, + { + Name: "show", + Usage: "Shows information about a security", + Action: ShowSecurity, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "security-id", Usage: "The security ID", Required: true}, + }, + }, { Name: "update-quote", Usage: "Triggers an update of one or more securities' quotes", @@ -58,65 +66,116 @@ var SecuritiesCmd = &cli.Command{ } // ListSecurities lists all securities. -func ListSecurities(ctx context.Context, cmd *cli.Command) error { +func ListSecurities(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - res, err := s.SecuritiesClient.ListSecurities(context.Background(), connect.NewRequest(&portfoliov1.ListSecuritiesRequest{})) + + var query struct { + Securities []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + } `json:"securities"` + } + + err = s.GraphQL.Query(context.Background(), &query, nil) if err != nil { return err } - fmt.Fprintln(cmd.Writer, res.Msg.Securities) + + s.WriteJSON(cmd.Writer, query) + return nil } -// UpdateQuote triggers an update of one or more securities' quotes. -func UpdateQuote(ctx context.Context, cmd *cli.Command) error { +// ShowSecurity shows information about a security. +func ShowSecurity(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - _, err := s.SecuritiesClient.TriggerSecurityQuoteUpdate( - context.Background(), - connect.NewRequest(&portfoliov1.TriggerQuoteUpdateRequest{ - SecurityIds: cmd.StringSlice("security-ids"), - }), - ) - return err + var query struct { + Security struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + ListedAs []struct { + Ticker string `json:"ticker"` + } `json:"listedAs"` + } `graphql:"security(id: $id)" json:"security"` + } + + err = s.GraphQL.Query(context.Background(), &query, map[string]interface{}{ + "id": graphql.String(cmd.String("security-id")), + }) + if err != nil { + return err + } + + s.WriteJSON(cmd.Writer, query) + + return nil } -// UpdateAllQuotes triggers an update of all quotes. -func UpdateAllQuotes(ctx context.Context, cmd *cli.Command) error { +// UpdateQuote triggers an update of one or more securities' quotes. +func UpdateQuote(ctx context.Context, cmd *cli.Command) (err error) { s := mcli.FromContext(ctx) - res, err := s.SecuritiesClient.ListSecurities(context.Background(), connect.NewRequest(&portfoliov1.ListSecuritiesRequest{})) + + var query struct { + TriggerQuoteUpdate []struct { + LatestQuote *currency.Currency `json:"latestQuote"` + } `graphql:"triggerQuoteUpdate(securityIDs: $IDs)" json:"updated"` + } + + var ids []graphql.String + for _, id := range cmd.StringSlice("security-ids") { + ids = append(ids, graphql.String(id)) + } + + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "IDs": ids, + }) if err != nil { return err } - var names []string + s.WriteJSON(cmd.Writer, query) - for _, sec := range res.Msg.Securities { - names = append(names, sec.Id) + return err +} + +// UpdateAllQuotes triggers an update of all quotes. +func UpdateAllQuotes(ctx context.Context, cmd *cli.Command) (err error) { + s := mcli.FromContext(ctx) + + var query struct { + TriggerQuoteUpdate []struct { + LatestQuote *currency.Currency `json:"latestQuote"` + } `graphql:"triggerQuoteUpdate(securityIDs: $IDs)" json:"updated"` } - _, err = s.SecuritiesClient.TriggerSecurityQuoteUpdate( - context.Background(), - connect.NewRequest(&portfoliov1.TriggerQuoteUpdateRequest{ - SecurityIds: names, - }), - ) + err = s.GraphQL.Mutate(context.Background(), &query, map[string]interface{}{ + "IDs": []graphql.String{}, + }) + if err != nil { + return err + } - return err + return } // PredictSecurities predicts the securities for shell completion. func PredictSecurities(ctx context.Context, cmd *cli.Command) { s := mcli.FromContext(ctx) - res, err := s.SecuritiesClient.ListSecurities( - context.Background(), - connect.NewRequest(&portfoliov1.ListSecuritiesRequest{}), - ) + + var query struct { + Securities []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + } `json:"securities"` + } + + err := s.GraphQL.Query(context.Background(), &query, nil) if err != nil { return } - for _, p := range res.Msg.Securities { - fmt.Fprintf(cmd.Root().Writer, "%s:%s\n", p.Id, p.DisplayName) + for _, p := range query.Securities { + fmt.Fprintf(cmd.Writer, "%s:%s\n", p.ID, p.DisplayName) } } diff --git a/cli/commands/securities_test.go b/cli/commands/securities_test.go index 6128e513..ca50ae66 100644 --- a/cli/commands/securities_test.go +++ b/cli/commands/securities_test.go @@ -19,27 +19,30 @@ package commands import ( "context" - "strings" "testing" - moneygopher "github.com/oxisto/money-gopher" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/internal" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/internal/testdata" "github.com/oxisto/money-gopher/internal/testing/clitest" + "github.com/oxisto/money-gopher/internal/testing/persistencetest" + "github.com/oxisto/money-gopher/internal/testing/quotetest" "github.com/oxisto/money-gopher/internal/testing/servertest" "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/securities/quote" "github.com/oxisto/assert" "github.com/urfave/cli/v3" ) func TestUpdateQuote(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { - ops := persistence.Ops[*portfoliov1.Security](db) - ops.Replace(&portfoliov1.Security{ - Id: "mysecurity", - QuoteProvider: moneygopher.Ref("mock"), - }) + srv := servertest.NewServer(persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) + assert.NoError(t, err) + + _, err = db.UpsertListedSecurity(context.Background(), testdata.TestUpsertListedSecurityParams) + assert.NoError(t, err) + + quote.RegisterQuoteProvider(quotetest.QuoteProviderStatic, quotetest.NewStaticQuoteProvider(currency.Value(100))) })) defer srv.Close() @@ -51,6 +54,7 @@ func TestUpdateQuote(t *testing.T) { name string args args wantErr bool + wantRec assert.Want[*clitest.CommandRecorder] }{ { name: "happy path", @@ -58,27 +62,48 @@ func TestUpdateQuote(t *testing.T) { ctx: clitest.NewSessionContext(t, srv), cmd: clitest.MockCommand(t, SecuritiesCmd.Command("update-quote").Flags, - "--security-ids", "mysecurity", + "--security-ids", testdata.TestCreateSecurityParams.ID, ), }, + wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { + return assert.Equals(t, `{ + "updated": [ + { + "latestQuote": { + "value": 100, + "symbol": "EUR" + } + } + ] +} +`, rec.String(), + ) + }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + rec := clitest.Record(tt.args.cmd) if err := UpdateQuote(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { t.Errorf("UpdateQuote() error = %v, wantErr %v", err, tt.wantErr) } + if tt.wantRec != nil { + tt.wantRec(t, rec) + } }) } } func TestUpdateAllQuotes(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { - ops := persistence.Ops[*portfoliov1.Security](db) - ops.Replace(&portfoliov1.Security{ - Id: "mysecurity", - QuoteProvider: moneygopher.Ref("mock"), - }) + srv := servertest.NewServer(persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) + assert.NoError(t, err) + + _, err = db.UpsertListedSecurity(context.Background(), testdata.TestUpsertListedSecurityParams) + assert.NoError(t, err) + + quote.RegisterQuoteProvider(quotetest.QuoteProviderStatic, quotetest.NewStaticQuoteProvider(currency.Value(100))) })) defer srv.Close() @@ -109,19 +134,8 @@ func TestUpdateAllQuotes(t *testing.T) { } func TestListSecurities(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t, func(db *persistence.DB) { - q := persistence.New(db) - _, err := q.CreateSecurity(context.Background(), persistence.CreateSecurityParams{ - ID: "1234", - DisplayName: "One Two Three Four", - }) - assert.NoError(t, err) - - _, err = q.UpsertListedSecurity(context.Background(), persistence.UpsertListedSecurityParams{ - SecurityID: "1234", - Ticker: "ONE", - Currency: "USD", - }) + srv := servertest.NewServer(persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) assert.NoError(t, err) })) defer srv.Close() @@ -143,18 +157,83 @@ func TestListSecurities(t *testing.T) { cmd: clitest.MockCommand(t, SecuritiesCmd.Command("list").Flags), }, wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { - return assert.Equals(t, true, strings.Contains(rec.String(), "1234")) + return assert.Equals(t, `{ + "securities": [ + { + "id": "DE1234567890", + "displayName": "My Security" + } + ] +} +`, rec.String()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rec := clitest.NewCommandRecorder() - tt.args.cmd.Writer = rec + rec := clitest.Record(tt.args.cmd) if err := ListSecurities(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { t.Errorf("ListSecurities() error = %v, wantErr %v", err, tt.wantErr) } + if tt.wantRec != nil { + tt.wantRec(t, rec) + } + }) + } +} + +func TestShowSecurity(t *testing.T) { + srv := servertest.NewServer(persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) + assert.NoError(t, err) + + _, err = db.UpsertListedSecurity(context.Background(), testdata.TestUpsertListedSecurityParams) + assert.NoError(t, err) + + quote.RegisterQuoteProvider(quotetest.QuoteProviderStatic, quotetest.NewStaticQuoteProvider(currency.Value(100))) + })) + defer srv.Close() + + type args struct { + ctx context.Context + cmd *cli.Command + } + tests := []struct { + name string + args args + wantErr bool + wantRec assert.Want[*clitest.CommandRecorder] + }{ + { + name: "happy path", + args: args{ + ctx: clitest.NewSessionContext(t, srv), + cmd: clitest.MockCommand(t, SecuritiesCmd.Command("show").Flags, "--security-id", "DE1234567890"), + }, + wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { + return assert.Equals(t, `{ + "security": { + "id": "DE1234567890", + "displayName": "My Security", + "listedAs": [ + { + "ticker": "TICK" + } + ] + } +} +`, rec.String()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := clitest.Record(tt.args.cmd) + if err := ShowSecurity(tt.args.ctx, tt.args.cmd); (err != nil) != tt.wantErr { + t.Errorf("ShowSecurity() error = %v, wantErr %v", err, tt.wantErr) + } if tt.wantRec != nil { tt.wantRec(t, rec) @@ -164,7 +243,10 @@ func TestListSecurities(t *testing.T) { } func TestPredictSecurities(t *testing.T) { - srv := servertest.NewServer(internal.NewTestDB(t)) + srv := servertest.NewServer(persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), testdata.TestCreateSecurityParams) + assert.NoError(t, err) + })) defer srv.Close() type args struct { @@ -183,7 +265,7 @@ func TestPredictSecurities(t *testing.T) { cmd: &cli.Command{}, }, wantRec: func(t *testing.T, rec *clitest.CommandRecorder) bool { - return assert.Equals(t, "US0378331005:Apple Inc.\n", rec.String()) + return assert.Equals(t, "DE1234567890:My Security\n", rec.String()) }, }, } diff --git a/cmd/mgo/mgo.go b/cmd/mgo/mgo.go index f2061338..a2d1d689 100644 --- a/cmd/mgo/mgo.go +++ b/cmd/mgo/mgo.go @@ -18,15 +18,14 @@ package main import ( "context" - "log/slog" + "fmt" "os" - "github.com/lmittmann/tint" "github.com/oxisto/money-gopher/cli/commands" ) func main() { if err := commands.CLICmd.Run(context.Background(), os.Args); err != nil { - slog.Error("Error while running command", tint.Err(err)) + fmt.Println(err) } } diff --git a/currency/currency.go b/currency/currency.go new file mode 100644 index 00000000..df2848ed --- /dev/null +++ b/currency/currency.go @@ -0,0 +1,114 @@ +package currency + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "math" +) + +// Currency represents a currency with a value and a symbol. +type Currency struct { + // Amount is the amount of the currency. + Amount int32 `json:"value"` + + // Symbol is the symbol of the currency. + Symbol string `json:"symbol"` +} + +func Zero() *Currency { + // TODO(oxisto): Somehow make it possible to change default currency + return &Currency{Symbol: "EUR"} +} + +func Value(v int32) *Currency { + // TODO(oxisto): Somehow make it possible to change default currency + return &Currency{Symbol: "EUR", Amount: v} +} + +func (c *Currency) PlusAssign(o *Currency) { + if o != nil { + c.Amount += o.Amount + } +} + +func (c *Currency) MinusAssign(o *Currency) { + if o != nil { + c.Amount -= o.Amount + } +} + +func Plus(a *Currency, b *Currency) *Currency { + return &Currency{ + Amount: a.Amount + b.Amount, + Symbol: a.Symbol, + } +} + +func (a *Currency) Plus(b *Currency) *Currency { + if b == nil { + return &Currency{ + Amount: a.Amount, + Symbol: a.Symbol, + } + } + + return &Currency{ + Amount: a.Amount + b.Amount, + Symbol: a.Symbol, + } +} + +func Minus(a *Currency, b *Currency) *Currency { + return &Currency{ + Amount: a.Amount - b.Amount, + Symbol: a.Symbol, + } +} + +func Divide(a *Currency, b float64) *Currency { + return &Currency{ + Amount: int32(math.Round((float64(a.Amount) / b))), + Symbol: a.Symbol, + } +} + +func Times(a *Currency, b float64) *Currency { + return &Currency{ + Amount: int32(math.Round((float64(a.Amount) * b))), + Symbol: a.Symbol, + } +} + +func (c *Currency) Pretty() string { + return fmt.Sprintf("%.0f %s", float32(c.Amount)/100, c.Symbol) +} + +func (c *Currency) IsZero() bool { + return c == nil || c.Amount == 0 +} + +// Value implements the driver.Valuer interface. +func (c *Currency) Value() (driver.Value, error) { + if c == nil { + return nil, nil + } + + return json.Marshal(c) +} + +// Scan implements the sql.Scanner interface. +func (c *Currency) Scan(src interface{}) error { + if src == nil { + return nil + } + + switch v := src.(type) { + case string: + return json.Unmarshal([]byte(v), c) + case []byte: + return json.Unmarshal(v, c) + default: + return fmt.Errorf("unsupported type: %T", src) + } +} diff --git a/finance/calculation.go b/finance/calculation.go index a7699c10..4d22c8e3 100644 --- a/finance/calculation.go +++ b/finance/calculation.go @@ -20,49 +20,54 @@ package finance import ( "math" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/events" ) // fifoTx is a helper struct to store transaction-related information in a FIFO // list. We basically need to copy the values from the original transaction, // since we need to modify it. type fifoTx struct { - amount float64 // amount of shares in this transaction - value *portfoliov1.Currency // value contains the net value of this transaction, i.e., without taxes and fees - fees *portfoliov1.Currency // fees contain any fees associated to this transaction - ppu *portfoliov1.Currency // ppu is the price per unit (amount) + amount float64 // amount of shares in this transaction + value *currency.Currency // value contains the net value of this transaction, i.e., without taxes and fees + fees *currency.Currency // fees contain any fees associated to this transaction + ppu *currency.Currency // ppu is the price per unit (amount) } +// calculation is a helper struct to calculate the net and gross value of a +// portfolio (snapshot). type calculation struct { Amount float64 - Fees *portfoliov1.Currency - Taxes *portfoliov1.Currency + Fees *currency.Currency + Taxes *currency.Currency - Cash *portfoliov1.Currency + Cash *currency.Currency fifo []*fifoTx } -func NewCalculation(txs []*portfoliov1.PortfolioEvent) *calculation { +// NewCalculation creates a new calculation struct and applies all events +func NewCalculation(events []*persistence.Transaction) *calculation { var c calculation - c.Fees = portfoliov1.Zero() - c.Taxes = portfoliov1.Zero() - c.Cash = portfoliov1.Zero() + c.Fees = currency.Zero() + c.Taxes = currency.Zero() + c.Cash = currency.Zero() - for _, tx := range txs { + for _, tx := range events { c.Apply(tx) } return &c } -func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) { +func (c *calculation) Apply(tx *persistence.Transaction) { switch tx.Type { - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND: + case events.PortfolioEventTypeDeliveryInbound: fallthrough - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY: + case events.PortfolioEventTypeBuy: var ( - total *portfoliov1.Currency + total *currency.Currency ) // Increase the amount of shares and the fees by the value stored in the @@ -70,7 +75,7 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) { c.Fees.PlusAssign(tx.Fees) c.Amount += tx.Amount - total = portfoliov1.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes) + total = currency.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes) // Decrease our cash c.Cash.MinusAssign(total) @@ -82,15 +87,15 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) { c.fifo = append(c.fifo, &fifoTx{ amount: tx.Amount, ppu: tx.Price, - value: portfoliov1.Times(tx.Price, tx.Amount), + value: currency.Times(tx.Price, tx.Amount), fees: tx.Fees, }) - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND: + case events.PortfolioEventTypeDeliveryOutbound: fallthrough - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL: + case events.PortfolioEventTypeSell: var ( sold float64 - total *portfoliov1.Currency + total *currency.Currency ) // Increase the fees and taxes by the value stored in the @@ -98,7 +103,7 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) { c.Fees.PlusAssign(tx.Fees) c.Taxes.PlusAssign(tx.Taxes) - total = portfoliov1.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes) + total = currency.Times(tx.Price, tx.Amount).Plus(tx.Fees).Plus(tx.Taxes) // Increase our cash c.Cash.PlusAssign(total) @@ -135,28 +140,28 @@ func (c *calculation) Apply(tx *portfoliov1.PortfolioEvent) { item.amount -= n // Adjust the value with the new amount - item.value = portfoliov1.Times(item.ppu, item.amount) + item.value = currency.Times(item.ppu, item.amount) // If no shares are left in this FIFO transaction, also remove the // fees, because they are now associated to the sale and not part of // the price calculation anymore. if item.amount <= 0 { - item.fees = portfoliov1.Zero() + item.fees = currency.Zero() } sold -= n } - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH: + case events.PortfolioEventTypeDepositCash: // Add to the cash c.Cash.PlusAssign(tx.Price) - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH: + case events.PortfolioEventTypeWithdrawCash: // Remove from the cash c.Cash.MinusAssign(tx.Price) } } -func (c *calculation) NetValue() (f *portfoliov1.Currency) { - f = portfoliov1.Zero() +func (c *calculation) NetValue() (f *currency.Currency) { + f = currency.Zero() for _, item := range c.fifo { f.PlusAssign(item.value) @@ -165,20 +170,20 @@ func (c *calculation) NetValue() (f *portfoliov1.Currency) { return } -func (c *calculation) GrossValue() (f *portfoliov1.Currency) { - f = portfoliov1.Zero() +func (c *calculation) GrossValue() (f *currency.Currency) { + f = currency.Zero() for _, item := range c.fifo { - f.PlusAssign(portfoliov1.Plus(item.value, item.fees)) + f.PlusAssign(currency.Plus(item.value, item.fees)) } return } -func (c *calculation) NetPrice() (f *portfoliov1.Currency) { - return portfoliov1.Divide(c.NetValue(), c.Amount) +func (c *calculation) NetPrice() (f *currency.Currency) { + return currency.Divide(c.NetValue(), c.Amount) } -func (c *calculation) GrossPrice() (f *portfoliov1.Currency) { - return portfoliov1.Divide(c.GrossValue(), c.Amount) +func (c *calculation) GrossPrice() (f *currency.Currency) { + return currency.Divide(c.GrossValue(), c.Amount) } diff --git a/finance/calculation_test.go b/finance/calculation_test.go index 811d8eea..8a872055 100644 --- a/finance/calculation_test.go +++ b/finance/calculation_test.go @@ -20,14 +20,15 @@ package finance import ( "testing" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/assert" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/events" ) func TestNewCalculation(t *testing.T) { type args struct { - txs []*portfoliov1.PortfolioEvent + txs []*persistence.Transaction } tests := []struct { name string @@ -37,67 +38,68 @@ func TestNewCalculation(t *testing.T) { { name: "buy and sell", args: args{ - txs: []*portfoliov1.PortfolioEvent{ + txs: []*persistence.Transaction{ { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH, - Price: portfoliov1.Value(500000), + Type: events.PortfolioEventTypeDepositCash, + Price: currency.Value(500000), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, + Type: events.PortfolioEventTypeBuy, Amount: 5, - Price: portfoliov1.Value(18110), - Fees: portfoliov1.Value(716), + Price: currency.Value(18110), + Fees: currency.Value(716), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, + Type: events.PortfolioEventTypeSell, Amount: 2, - Price: portfoliov1.Value(30430), - Fees: portfoliov1.Value(642), - Taxes: portfoliov1.Value(1632), + Price: currency.Value(30430), + Fees: currency.Value(642), + Taxes: currency.Value(1632), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, + Type: events.PortfolioEventTypeBuy, Amount: 5, - Price: portfoliov1.Value(29000), - Fees: portfoliov1.Value(853), + Price: currency.Value(29000), + Fees: currency.Value(853), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, + Type: events.PortfolioEventTypeSell, Amount: 3, - Price: portfoliov1.Value(22000), - Fees: portfoliov1.Value(845), + Price: currency.Value(22000), + Fees: currency.Value(845), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, + Type: events.PortfolioEventTypeBuy, Amount: 5, - Price: portfoliov1.Value(20330), - Fees: portfoliov1.Value(744), + Price: currency.Value(20330), + Fees: currency.Value(744), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, + Type: events.PortfolioEventTypeBuy, Amount: 5, - Price: portfoliov1.Value(19645), - Fees: portfoliov1.Value(736), + Price: currency.Value(19645), + Fees: currency.Value(736), }, { - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, + Type: events.PortfolioEventTypeBuy, Amount: 10, - Price: portfoliov1.Value(14655), - Fees: portfoliov1.Value(856), + Price: currency.Value(14655), + Fees: currency.Value(856), }, }, }, want: func(t *testing.T, c *calculation) bool { return true && assert.Equals(t, 25, c.Amount) && - assert.Equals(t, 491425, int(c.NetValue().Value)) && - assert.Equals(t, 494614, int(c.GrossValue().Value)) && - assert.Equals(t, 19657, int(c.NetPrice().Value)) && - assert.Equals(t, 19785, int(c.GrossPrice().Value)) && - assert.Equals(t, 44099, int(c.Cash.Value)) + assert.Equals(t, 491425, int(c.NetValue().Amount)) && + assert.Equals(t, 494614, int(c.GrossValue().Amount)) && + assert.Equals(t, 19657, int(c.NetPrice().Amount)) && + assert.Equals(t, 19785, int(c.GrossPrice().Amount)) && + assert.Equals(t, 44099, int(c.Cash.Amount)) }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := NewCalculation(tt.args.txs) diff --git a/finance/snapshot.go b/finance/snapshot.go new file mode 100644 index 00000000..386db23a --- /dev/null +++ b/finance/snapshot.go @@ -0,0 +1,183 @@ +package finance + +import ( + "context" + "fmt" + "maps" + "slices" + "time" + + moneygopher "github.com/oxisto/money-gopher" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/models" + "github.com/oxisto/money-gopher/persistence" +) + +// SnapshotDataProvider is an interface that provides the necessary data for +// building a snapshot. It includes methods for retrieving portfolio events and +// securities by their IDs. +type SnapshotDataProvider interface { + ListListedSecuritiesBySecurityID(ctx context.Context, securityID string) ([]*persistence.ListedSecurity, error) + ListTransactionsByPortfolioID(ctx context.Context, portfolioID string) ([]*persistence.Transaction, error) + ListSecuritiesByIDs(ctx context.Context, ids []string) ([]*persistence.Security, error) +} + +// BuildSnapshot creates a snapshot of the portfolio at a given time. It +// calculates the performance and market value of the current positions and the +// total value of the portfolio. +// +// The snapshot is built by retrieving all events and security information from +// a [SnapshotDataProvider]. The snapshot is built by iterating over the events +// and calculating the positions at the specified timestamp. +func BuildSnapshot( + ctx context.Context, + timestamp time.Time, + portfolioID string, + provider SnapshotDataProvider, +) (snap *models.PortfolioSnapshot, err error) { + var ( + events []*persistence.Transaction + m map[string][]*persistence.Transaction + ids []string + secs []*persistence.Security + secmap map[string]*persistence.Security + ) + + // Retrieve events + events, err = provider.ListTransactionsByPortfolioID(ctx, portfolioID) + if err != nil { + return nil, err + } + + // Set up the snapshot + snap = &models.PortfolioSnapshot{ + Time: timestamp, + Positions: make([]*models.PortfolioPosition, 0), + TotalPurchaseValue: currency.Zero(), + TotalMarketValue: currency.Zero(), + TotalProfitOrLoss: currency.Zero(), + Cash: currency.Zero(), + } + + // Record the first transaction time + if len(events) > 0 { + snap.FirstTransactionTime = events[0].Time + } + + // Retrieve the event map; a map of events indexed by their security ID + m = groupByPortfolio(events) + ids = slices.Collect(maps.Keys(m)) + + // Retrieve market value of filtered securities + secs, err = provider.ListSecuritiesByIDs(context.Background(), ids) + + if err != nil { + return nil, fmt.Errorf("internal error while calling ListSecurities on securities service: %w", err) + } + + // Make a map out of the securities list so we can access it easier + secmap = moneygopher.Map(secs, func(s *persistence.Security) string { + return s.ID + }) + + // We need to look at the portfolio events up to the time of the snapshot + // and calculate the current positions. + for name, txs := range m { + txs = eventsBefore(txs, timestamp) + + c := NewCalculation(txs) + + if name == "cash" { + // Add deposited/withdrawn cash directly + snap.Cash.PlusAssign(c.Cash) + continue + } + + if c.Amount == 0 { + continue + } + + // Also add cash that is part of a securities' transaction (e.g., sell/buy) + snap.Cash.PlusAssign(c.Cash) + + pos := &models.PortfolioPosition{ + Security: secmap[name], + Amount: c.Amount, + PurchaseValue: c.NetValue(), + PurchasePrice: c.NetPrice(), + MarketValue: currency.Times(marketPrice(name, c.NetPrice(), provider), c.Amount), + MarketPrice: marketPrice(name, c.NetPrice(), provider), + } + + // Calculate loss and gains + pos.ProfitOrLoss = currency.Minus(pos.MarketValue, pos.PurchaseValue) + pos.Gains = float64(currency.Minus(pos.MarketValue, pos.PurchaseValue).Amount) / float64(pos.PurchaseValue.Amount) + + // Add to total value(s) + snap.TotalPurchaseValue.PlusAssign(pos.PurchaseValue) + snap.TotalMarketValue.PlusAssign(pos.MarketValue) + snap.TotalProfitOrLoss.PlusAssign(pos.ProfitOrLoss) + + // Store position in map + snap.Positions = append(snap.Positions, pos) + } + + // Calculate total gains. We try to avoid division by zero. + if snap.TotalPurchaseValue.Amount == 0 { + snap.TotalGains = 0 + } else { + snap.TotalGains = float64(currency.Minus(snap.TotalMarketValue, snap.TotalPurchaseValue).Amount) / float64(snap.TotalPurchaseValue.Amount) + } + + // Calculate total portfolio value + snap.TotalPortfolioValue = snap.TotalMarketValue.Plus(snap.Cash) + + return snap, nil +} + +// eventsBefore returns all events that occurred before a given time. +// TODO: move to SQL query +func eventsBefore(events []*persistence.Transaction, t time.Time) (out []*persistence.Transaction) { + out = make([]*persistence.Transaction, 0, len(events)) + + for _, event := range events { + if event.Time.After(t) { + continue + } + + out = append(out, event) + } + + return +} + +// groupByPortfolio groups the events by their security ID. +func groupByPortfolio(events []*persistence.Transaction) (m map[string][]*persistence.Transaction) { + m = make(map[string][]*persistence.Transaction) + + for _, event := range events { + name := event.SecurityID + if name != nil { + m[*name] = append(m[*name], event) + } else { + // a little bit of a hack + m["cash"] = append(m["cash"], event) + } + } + + return +} + +func marketPrice( + name string, + netPrice *currency.Currency, + provider SnapshotDataProvider, +) *currency.Currency { + ls, _ := provider.ListListedSecuritiesBySecurityID(context.Background(), name) + + if ls == nil || ls[0].LatestQuote == nil { + return netPrice + } else { + return ls[0].LatestQuote + } +} diff --git a/gen/account_sql.go b/gen/account_sql.go deleted file mode 100644 index e6ec5509..00000000 --- a/gen/account_sql.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "database/sql" - "errors" - "strings" - - "github.com/oxisto/money-gopher/persistence" -) - -func (*BankAccount) InitTables(db *persistence.DB) (err error) { - _, err1 := db.Exec(`CREATE TABLE IF NOT EXISTS bank_accounts ( -id TEXT PRIMARY KEY, -display_name TEXT NOT NULL -);`) - err2 := (&PortfolioEvent{}).InitTables(db) - - return errors.Join(err1, err2) -} - -func (*BankAccount) PrepareReplace(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`REPLACE INTO bank_accounts (id, display_name) VALUES (?,?);`) -} - -func (*BankAccount) PrepareList(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name FROM bank_accounts`) -} - -func (*BankAccount) PrepareGet(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name FROM bank_accounts WHERE id = ?`) -} - -func (*BankAccount) PrepareUpdate(db *persistence.DB, columns []string) (stmt *sql.Stmt, err error) { - // We need to make sure to quote columns here because they are potentially evil user input - var ( - query string - set []string - ) - - set = make([]string, len(columns)) - for i, col := range columns { - set[i] = persistence.Quote(col) + " = ?" - } - - query += "UPDATE bank_accounts SET " + strings.Join(set, ", ") + " WHERE id = ?;" - - return db.Prepare(query) -} - -func (*BankAccount) PrepareDelete(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`DELETE FROM bank_accounts WHERE id = ?`) -} - -func (p *BankAccount) ReplaceIntoArgs() []any { - return []any{p.Id, p.DisplayName} -} - -func (p *BankAccount) UpdateArgs(columns []string) (args []any) { - for _, col := range columns { - switch col { - case "id": - args = append(args, p.Id) - case "display_name": - args = append(args, p.DisplayName) - } - } - - return args -} - -func (*BankAccount) Scan(sc persistence.Scanner) (obj persistence.StorageObject, err error) { - var ( - acc BankAccount - ) - - err = sc.Scan(&acc.Id, &acc.DisplayName) - if err != nil { - return nil, err - } - - return &acc, nil -} diff --git a/gen/currency.go b/gen/currency.go deleted file mode 100644 index 62e2230a..00000000 --- a/gen/currency.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "fmt" - "math" -) - -func Zero() *Currency { - // TODO(oxisto): Somehow make it possible to change default currency - return &Currency{Symbol: "EUR"} -} - -func Value(v int32) *Currency { - // TODO(oxisto): Somehow make it possible to change default currency - return &Currency{Symbol: "EUR", Value: v} -} - -func (c *Currency) PlusAssign(o *Currency) { - if o != nil { - c.Value += o.Value - } -} - -func (c *Currency) MinusAssign(o *Currency) { - if o != nil { - c.Value -= o.Value - } -} - -func Plus(a *Currency, b *Currency) *Currency { - return &Currency{ - Value: a.Value + b.Value, - Symbol: a.Symbol, - } -} - -func (a *Currency) Plus(b *Currency) *Currency { - if b == nil { - return &Currency{ - Value: a.Value, - Symbol: a.Symbol, - } - } - - return &Currency{ - Value: a.Value + b.Value, - Symbol: a.Symbol, - } -} - -func Minus(a *Currency, b *Currency) *Currency { - return &Currency{ - Value: a.Value - b.Value, - Symbol: a.Symbol, - } -} - -func Divide(a *Currency, b float64) *Currency { - return &Currency{ - Value: int32(math.Round((float64(a.Value) / b))), - Symbol: a.Symbol, - } -} - -func Times(a *Currency, b float64) *Currency { - return &Currency{ - Value: int32(math.Round((float64(a.Value) * b))), - Symbol: a.Symbol, - } -} - -func (c *Currency) Pretty() string { - return fmt.Sprintf("%.0f %s", float32(c.Value)/100, c.Symbol) -} - -func (c *Currency) IsZero() bool { - return c == nil || c.Value == 0 -} diff --git a/gen/mgo.pb.go b/gen/mgo.pb.go deleted file mode 100644 index d86f324d..00000000 --- a/gen/mgo.pb.go +++ /dev/null @@ -1,2646 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.1 -// protoc (unknown) -// source: mgo.proto - -package portfoliov1 - -import ( - _ "google.golang.org/genproto/googleapis/api/annotations" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" - 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) -) - -type PortfolioEventType int32 - -const ( - PortfolioEventType_PORTFOLIO_EVENT_TYPE_UNSPECIFIED PortfolioEventType = 0 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY PortfolioEventType = 1 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL PortfolioEventType = 2 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND PortfolioEventType = 3 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND PortfolioEventType = 4 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_DIVIDEND PortfolioEventType = 10 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_INTEREST PortfolioEventType = 11 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH PortfolioEventType = 20 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH PortfolioEventType = 21 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_ACCOUNT_FEES PortfolioEventType = 30 - PortfolioEventType_PORTFOLIO_EVENT_TYPE_TAX_REFUND PortfolioEventType = 31 -) - -// Enum value maps for PortfolioEventType. -var ( - PortfolioEventType_name = map[int32]string{ - 0: "PORTFOLIO_EVENT_TYPE_UNSPECIFIED", - 1: "PORTFOLIO_EVENT_TYPE_BUY", - 2: "PORTFOLIO_EVENT_TYPE_SELL", - 3: "PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND", - 4: "PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND", - 10: "PORTFOLIO_EVENT_TYPE_DIVIDEND", - 11: "PORTFOLIO_EVENT_TYPE_INTEREST", - 20: "PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH", - 21: "PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH", - 30: "PORTFOLIO_EVENT_TYPE_ACCOUNT_FEES", - 31: "PORTFOLIO_EVENT_TYPE_TAX_REFUND", - } - PortfolioEventType_value = map[string]int32{ - "PORTFOLIO_EVENT_TYPE_UNSPECIFIED": 0, - "PORTFOLIO_EVENT_TYPE_BUY": 1, - "PORTFOLIO_EVENT_TYPE_SELL": 2, - "PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND": 3, - "PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND": 4, - "PORTFOLIO_EVENT_TYPE_DIVIDEND": 10, - "PORTFOLIO_EVENT_TYPE_INTEREST": 11, - "PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH": 20, - "PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH": 21, - "PORTFOLIO_EVENT_TYPE_ACCOUNT_FEES": 30, - "PORTFOLIO_EVENT_TYPE_TAX_REFUND": 31, - } -) - -func (x PortfolioEventType) Enum() *PortfolioEventType { - p := new(PortfolioEventType) - *p = x - return p -} - -func (x PortfolioEventType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (PortfolioEventType) Descriptor() protoreflect.EnumDescriptor { - return file_mgo_proto_enumTypes[0].Descriptor() -} - -func (PortfolioEventType) Type() protoreflect.EnumType { - return &file_mgo_proto_enumTypes[0] -} - -func (x PortfolioEventType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use PortfolioEventType.Descriptor instead. -func (PortfolioEventType) EnumDescriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{0} -} - -// Currency is a currency value in the lowest unit of the selected currency -// (e.g., cents for EUR/USD). -type Currency struct { - state protoimpl.MessageState `protogen:"open.v1"` - Value int32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` - Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Currency) Reset() { - *x = Currency{} - mi := &file_mgo_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Currency) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Currency) ProtoMessage() {} - -func (x *Currency) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Currency.ProtoReflect.Descriptor instead. -func (*Currency) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{0} -} - -func (x *Currency) GetValue() int32 { - if x != nil { - return x.Value - } - return 0 -} - -func (x *Currency) GetSymbol() string { - if x != nil { - return x.Symbol - } - return "" -} - -type CreatePortfolioRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Portfolio *Portfolio `protobuf:"bytes,1,opt,name=portfolio,proto3" json:"portfolio,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreatePortfolioRequest) Reset() { - *x = CreatePortfolioRequest{} - mi := &file_mgo_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreatePortfolioRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreatePortfolioRequest) ProtoMessage() {} - -func (x *CreatePortfolioRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreatePortfolioRequest.ProtoReflect.Descriptor instead. -func (*CreatePortfolioRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{1} -} - -func (x *CreatePortfolioRequest) GetPortfolio() *Portfolio { - if x != nil { - return x.Portfolio - } - return nil -} - -type ListPortfoliosRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListPortfoliosRequest) Reset() { - *x = ListPortfoliosRequest{} - mi := &file_mgo_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListPortfoliosRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListPortfoliosRequest) ProtoMessage() {} - -func (x *ListPortfoliosRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListPortfoliosRequest.ProtoReflect.Descriptor instead. -func (*ListPortfoliosRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{2} -} - -type ListPortfoliosResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Portfolios []*Portfolio `protobuf:"bytes,1,rep,name=portfolios,proto3" json:"portfolios,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListPortfoliosResponse) Reset() { - *x = ListPortfoliosResponse{} - mi := &file_mgo_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListPortfoliosResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListPortfoliosResponse) ProtoMessage() {} - -func (x *ListPortfoliosResponse) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListPortfoliosResponse.ProtoReflect.Descriptor instead. -func (*ListPortfoliosResponse) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{3} -} - -func (x *ListPortfoliosResponse) GetPortfolios() []*Portfolio { - if x != nil { - return x.Portfolios - } - return nil -} - -type GetPortfolioRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetPortfolioRequest) Reset() { - *x = GetPortfolioRequest{} - mi := &file_mgo_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetPortfolioRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioRequest) ProtoMessage() {} - -func (x *GetPortfolioRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioRequest.ProtoReflect.Descriptor instead. -func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{4} -} - -func (x *GetPortfolioRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type UpdatePortfolioRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Portfolio *Portfolio `protobuf:"bytes,1,opt,name=portfolio,proto3" json:"portfolio,omitempty"` - UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=updateMask,proto3" json:"updateMask,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdatePortfolioRequest) Reset() { - *x = UpdatePortfolioRequest{} - mi := &file_mgo_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdatePortfolioRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdatePortfolioRequest) ProtoMessage() {} - -func (x *UpdatePortfolioRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdatePortfolioRequest.ProtoReflect.Descriptor instead. -func (*UpdatePortfolioRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{5} -} - -func (x *UpdatePortfolioRequest) GetPortfolio() *Portfolio { - if x != nil { - return x.Portfolio - } - return nil -} - -func (x *UpdatePortfolioRequest) GetUpdateMask() *fieldmaskpb.FieldMask { - if x != nil { - return x.UpdateMask - } - return nil -} - -type DeletePortfolioRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeletePortfolioRequest) Reset() { - *x = DeletePortfolioRequest{} - mi := &file_mgo_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeletePortfolioRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeletePortfolioRequest) ProtoMessage() {} - -func (x *DeletePortfolioRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeletePortfolioRequest.ProtoReflect.Descriptor instead. -func (*DeletePortfolioRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{6} -} - -func (x *DeletePortfolioRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type GetPortfolioSnapshotRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // PortfolioId is the identifier of the portfolio we want to - // "snapshot". - PortfolioId string `protobuf:"bytes,1,opt,name=portfolio_id,json=portfolioId,proto3" json:"portfolio_id,omitempty"` - // Time is the point in time of the requested snapshot. - Time *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=time,proto3" json:"time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetPortfolioSnapshotRequest) Reset() { - *x = GetPortfolioSnapshotRequest{} - mi := &file_mgo_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetPortfolioSnapshotRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioSnapshotRequest) ProtoMessage() {} - -func (x *GetPortfolioSnapshotRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioSnapshotRequest.ProtoReflect.Descriptor instead. -func (*GetPortfolioSnapshotRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{7} -} - -func (x *GetPortfolioSnapshotRequest) GetPortfolioId() string { - if x != nil { - return x.PortfolioId - } - return "" -} - -func (x *GetPortfolioSnapshotRequest) GetTime() *timestamppb.Timestamp { - if x != nil { - return x.Time - } - return nil -} - -type CreatePortfolioTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Transaction *PortfolioEvent `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreatePortfolioTransactionRequest) Reset() { - *x = CreatePortfolioTransactionRequest{} - mi := &file_mgo_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreatePortfolioTransactionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreatePortfolioTransactionRequest) ProtoMessage() {} - -func (x *CreatePortfolioTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreatePortfolioTransactionRequest.ProtoReflect.Descriptor instead. -func (*CreatePortfolioTransactionRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{8} -} - -func (x *CreatePortfolioTransactionRequest) GetTransaction() *PortfolioEvent { - if x != nil { - return x.Transaction - } - return nil -} - -type GetPortfolioTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetPortfolioTransactionRequest) Reset() { - *x = GetPortfolioTransactionRequest{} - mi := &file_mgo_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetPortfolioTransactionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioTransactionRequest) ProtoMessage() {} - -func (x *GetPortfolioTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioTransactionRequest.ProtoReflect.Descriptor instead. -func (*GetPortfolioTransactionRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{9} -} - -func (x *GetPortfolioTransactionRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type ListPortfolioTransactionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - PortfolioId string `protobuf:"bytes,1,opt,name=portfolio_id,json=portfolioId,proto3" json:"portfolio_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListPortfolioTransactionsRequest) Reset() { - *x = ListPortfolioTransactionsRequest{} - mi := &file_mgo_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListPortfolioTransactionsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListPortfolioTransactionsRequest) ProtoMessage() {} - -func (x *ListPortfolioTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListPortfolioTransactionsRequest.ProtoReflect.Descriptor instead. -func (*ListPortfolioTransactionsRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{10} -} - -func (x *ListPortfolioTransactionsRequest) GetPortfolioId() string { - if x != nil { - return x.PortfolioId - } - return "" -} - -type ListPortfolioTransactionsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Transactions []*PortfolioEvent `protobuf:"bytes,1,rep,name=transactions,proto3" json:"transactions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListPortfolioTransactionsResponse) Reset() { - *x = ListPortfolioTransactionsResponse{} - mi := &file_mgo_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListPortfolioTransactionsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListPortfolioTransactionsResponse) ProtoMessage() {} - -func (x *ListPortfolioTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListPortfolioTransactionsResponse.ProtoReflect.Descriptor instead. -func (*ListPortfolioTransactionsResponse) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{11} -} - -func (x *ListPortfolioTransactionsResponse) GetTransactions() []*PortfolioEvent { - if x != nil { - return x.Transactions - } - return nil -} - -type UpdatePortfolioTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Transaction *PortfolioEvent `protobuf:"bytes,1,opt,name=transaction,proto3" json:"transaction,omitempty"` - UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=updateMask,proto3" json:"updateMask,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdatePortfolioTransactionRequest) Reset() { - *x = UpdatePortfolioTransactionRequest{} - mi := &file_mgo_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdatePortfolioTransactionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdatePortfolioTransactionRequest) ProtoMessage() {} - -func (x *UpdatePortfolioTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdatePortfolioTransactionRequest.ProtoReflect.Descriptor instead. -func (*UpdatePortfolioTransactionRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{12} -} - -func (x *UpdatePortfolioTransactionRequest) GetTransaction() *PortfolioEvent { - if x != nil { - return x.Transaction - } - return nil -} - -func (x *UpdatePortfolioTransactionRequest) GetUpdateMask() *fieldmaskpb.FieldMask { - if x != nil { - return x.UpdateMask - } - return nil -} - -type DeletePortfolioTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - TransactionId int32 `protobuf:"varint,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeletePortfolioTransactionRequest) Reset() { - *x = DeletePortfolioTransactionRequest{} - mi := &file_mgo_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeletePortfolioTransactionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeletePortfolioTransactionRequest) ProtoMessage() {} - -func (x *DeletePortfolioTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeletePortfolioTransactionRequest.ProtoReflect.Descriptor instead. -func (*DeletePortfolioTransactionRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{13} -} - -func (x *DeletePortfolioTransactionRequest) GetTransactionId() int32 { - if x != nil { - return x.TransactionId - } - return 0 -} - -type ImportTransactionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - PortfolioId string `protobuf:"bytes,1,opt,name=portfolio_id,json=portfolioId,proto3" json:"portfolio_id,omitempty"` - FromCsv string `protobuf:"bytes,2,opt,name=from_csv,json=fromCsv,proto3" json:"from_csv,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ImportTransactionsRequest) Reset() { - *x = ImportTransactionsRequest{} - mi := &file_mgo_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ImportTransactionsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ImportTransactionsRequest) ProtoMessage() {} - -func (x *ImportTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[14] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ImportTransactionsRequest.ProtoReflect.Descriptor instead. -func (*ImportTransactionsRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{14} -} - -func (x *ImportTransactionsRequest) GetPortfolioId() string { - if x != nil { - return x.PortfolioId - } - return "" -} - -func (x *ImportTransactionsRequest) GetFromCsv() string { - if x != nil { - return x.FromCsv - } - return "" -} - -type CreateBankAccountRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - BankAccount *BankAccount `protobuf:"bytes,1,opt,name=bank_account,json=bankAccount,proto3" json:"bank_account,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreateBankAccountRequest) Reset() { - *x = CreateBankAccountRequest{} - mi := &file_mgo_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreateBankAccountRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateBankAccountRequest) ProtoMessage() {} - -func (x *CreateBankAccountRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[15] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateBankAccountRequest.ProtoReflect.Descriptor instead. -func (*CreateBankAccountRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{15} -} - -func (x *CreateBankAccountRequest) GetBankAccount() *BankAccount { - if x != nil { - return x.BankAccount - } - return nil -} - -type UpdateBankAccountRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Account *BankAccount `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` - UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=updateMask,proto3" json:"updateMask,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateBankAccountRequest) Reset() { - *x = UpdateBankAccountRequest{} - mi := &file_mgo_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateBankAccountRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateBankAccountRequest) ProtoMessage() {} - -func (x *UpdateBankAccountRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[16] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateBankAccountRequest.ProtoReflect.Descriptor instead. -func (*UpdateBankAccountRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{16} -} - -func (x *UpdateBankAccountRequest) GetAccount() *BankAccount { - if x != nil { - return x.Account - } - return nil -} - -func (x *UpdateBankAccountRequest) GetUpdateMask() *fieldmaskpb.FieldMask { - if x != nil { - return x.UpdateMask - } - return nil -} - -type DeleteBankAccountRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteBankAccountRequest) Reset() { - *x = DeleteBankAccountRequest{} - mi := &file_mgo_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteBankAccountRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteBankAccountRequest) ProtoMessage() {} - -func (x *DeleteBankAccountRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[17] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteBankAccountRequest.ProtoReflect.Descriptor instead. -func (*DeleteBankAccountRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{17} -} - -func (x *DeleteBankAccountRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type Portfolio struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - // BankAccountId contains the id/identifier of the underlying bank - // account. - BankAccountId string `protobuf:"bytes,3,opt,name=bank_account_id,json=bankAccountId,proto3" json:"bank_account_id,omitempty"` - // Events contains all portfolio events, such as buy/sell transactions, - // dividends or other. They need to be ordered by time (ascending). - Events []*PortfolioEvent `protobuf:"bytes,5,rep,name=events,proto3" json:"events,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Portfolio) Reset() { - *x = Portfolio{} - mi := &file_mgo_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Portfolio) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Portfolio) ProtoMessage() {} - -func (x *Portfolio) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[18] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Portfolio.ProtoReflect.Descriptor instead. -func (*Portfolio) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{18} -} - -func (x *Portfolio) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Portfolio) GetDisplayName() string { - if x != nil { - return x.DisplayName - } - return "" -} - -func (x *Portfolio) GetBankAccountId() string { - if x != nil { - return x.BankAccountId - } - return "" -} - -func (x *Portfolio) GetEvents() []*PortfolioEvent { - if x != nil { - return x.Events - } - return nil -} - -type BankAccount struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *BankAccount) Reset() { - *x = BankAccount{} - mi := &file_mgo_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *BankAccount) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*BankAccount) ProtoMessage() {} - -func (x *BankAccount) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[19] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use BankAccount.ProtoReflect.Descriptor instead. -func (*BankAccount) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{19} -} - -func (x *BankAccount) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *BankAccount) GetDisplayName() string { - if x != nil { - return x.DisplayName - } - return "" -} - -// PortfolioSnapshot represents a snapshot in time of the portfolio. It can for -// example be the current state of the portfolio but also represent the state of -// the portfolio at a certain time in the past. -type PortfolioSnapshot struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Time is the time when this snapshot was taken. - Time *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=time,proto3" json:"time,omitempty"` - // Positions holds the current positions within the snapshot and their value. - Positions map[string]*PortfolioPosition `protobuf:"bytes,2,rep,name=positions,proto3" json:"positions,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // FirstTransactionTime is the time of the first transaction with the - // snapshot. - FirstTransactionTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=first_transaction_time,json=firstTransactionTime,proto3,oneof" json:"first_transaction_time,omitempty"` - // TotalPurchaseValue contains the total purchase value of all asset positions - TotalPurchaseValue *Currency `protobuf:"bytes,10,opt,name=total_purchase_value,json=totalPurchaseValue,proto3" json:"total_purchase_value,omitempty"` - // TotalMarketValue contains the total market value of all asset positions - TotalMarketValue *Currency `protobuf:"bytes,11,opt,name=total_market_value,json=totalMarketValue,proto3" json:"total_market_value,omitempty"` - // TotalProfitOrLoss contains the total absolute amount of profit or loss in - // this snapshot, based on asset value. - TotalProfitOrLoss *Currency `protobuf:"bytes,20,opt,name=total_profit_or_loss,json=totalProfitOrLoss,proto3" json:"total_profit_or_loss,omitempty"` - // TotalGains contains the total relative amount of profit or loss in this - // snapshot, based on asset value. - TotalGains float64 `protobuf:"fixed64,21,opt,name=total_gains,json=totalGains,proto3" json:"total_gains,omitempty"` - // Cash contains the current amount of cash in the portfolio's bank - // account(s). - Cash *Currency `protobuf:"bytes,22,opt,name=cash,proto3" json:"cash,omitempty"` - // TotalPortfolioValue contains the amount of cash plus the total market value - // of all assets. - TotalPortfolioValue *Currency `protobuf:"bytes,23,opt,name=total_portfolio_value,json=totalPortfolioValue,proto3" json:"total_portfolio_value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PortfolioSnapshot) Reset() { - *x = PortfolioSnapshot{} - mi := &file_mgo_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PortfolioSnapshot) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PortfolioSnapshot) ProtoMessage() {} - -func (x *PortfolioSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[20] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PortfolioSnapshot.ProtoReflect.Descriptor instead. -func (*PortfolioSnapshot) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{20} -} - -func (x *PortfolioSnapshot) GetTime() *timestamppb.Timestamp { - if x != nil { - return x.Time - } - return nil -} - -func (x *PortfolioSnapshot) GetPositions() map[string]*PortfolioPosition { - if x != nil { - return x.Positions - } - return nil -} - -func (x *PortfolioSnapshot) GetFirstTransactionTime() *timestamppb.Timestamp { - if x != nil { - return x.FirstTransactionTime - } - return nil -} - -func (x *PortfolioSnapshot) GetTotalPurchaseValue() *Currency { - if x != nil { - return x.TotalPurchaseValue - } - return nil -} - -func (x *PortfolioSnapshot) GetTotalMarketValue() *Currency { - if x != nil { - return x.TotalMarketValue - } - return nil -} - -func (x *PortfolioSnapshot) GetTotalProfitOrLoss() *Currency { - if x != nil { - return x.TotalProfitOrLoss - } - return nil -} - -func (x *PortfolioSnapshot) GetTotalGains() float64 { - if x != nil { - return x.TotalGains - } - return 0 -} - -func (x *PortfolioSnapshot) GetCash() *Currency { - if x != nil { - return x.Cash - } - return nil -} - -func (x *PortfolioSnapshot) GetTotalPortfolioValue() *Currency { - if x != nil { - return x.TotalPortfolioValue - } - return nil -} - -type PortfolioPosition struct { - state protoimpl.MessageState `protogen:"open.v1"` - Security *Security `protobuf:"bytes,1,opt,name=security,proto3" json:"security,omitempty"` - Amount float64 `protobuf:"fixed64,2,opt,name=amount,proto3" json:"amount,omitempty"` - // PurchaseValue was the market value of this position when it was bought - // (net; exclusive of any fees). - PurchaseValue *Currency `protobuf:"bytes,5,opt,name=purchase_value,json=purchaseValue,proto3" json:"purchase_value,omitempty"` - // PurchasePrice was the market price of this position when it was bought - // (net; exclusive of any fees). - PurchasePrice *Currency `protobuf:"bytes,6,opt,name=purchase_price,json=purchasePrice,proto3" json:"purchase_price,omitempty"` - // MarketValue is the current market value of this position, as retrieved from - // the securities service. - MarketValue *Currency `protobuf:"bytes,10,opt,name=market_value,json=marketValue,proto3" json:"market_value,omitempty"` - // MarketPrice is the current market price of this position, as retrieved from - // the securities service. - MarketPrice *Currency `protobuf:"bytes,11,opt,name=market_price,json=marketPrice,proto3" json:"market_price,omitempty"` - // TotalFees is the total amount of fees accumulating in this position through - // various transactions. - TotalFees *Currency `protobuf:"bytes,15,opt,name=total_fees,json=totalFees,proto3" json:"total_fees,omitempty"` - // ProfitOrLoss contains the absolute amount of profit or loss in this - // position. - ProfitOrLoss *Currency `protobuf:"bytes,20,opt,name=profit_or_loss,json=profitOrLoss,proto3" json:"profit_or_loss,omitempty"` - // Gains contains the relative amount of profit or loss in this position. - Gains float64 `protobuf:"fixed64,21,opt,name=gains,proto3" json:"gains,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PortfolioPosition) Reset() { - *x = PortfolioPosition{} - mi := &file_mgo_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PortfolioPosition) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PortfolioPosition) ProtoMessage() {} - -func (x *PortfolioPosition) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[21] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PortfolioPosition.ProtoReflect.Descriptor instead. -func (*PortfolioPosition) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{21} -} - -func (x *PortfolioPosition) GetSecurity() *Security { - if x != nil { - return x.Security - } - return nil -} - -func (x *PortfolioPosition) GetAmount() float64 { - if x != nil { - return x.Amount - } - return 0 -} - -func (x *PortfolioPosition) GetPurchaseValue() *Currency { - if x != nil { - return x.PurchaseValue - } - return nil -} - -func (x *PortfolioPosition) GetPurchasePrice() *Currency { - if x != nil { - return x.PurchasePrice - } - return nil -} - -func (x *PortfolioPosition) GetMarketValue() *Currency { - if x != nil { - return x.MarketValue - } - return nil -} - -func (x *PortfolioPosition) GetMarketPrice() *Currency { - if x != nil { - return x.MarketPrice - } - return nil -} - -func (x *PortfolioPosition) GetTotalFees() *Currency { - if x != nil { - return x.TotalFees - } - return nil -} - -func (x *PortfolioPosition) GetProfitOrLoss() *Currency { - if x != nil { - return x.ProfitOrLoss - } - return nil -} - -func (x *PortfolioPosition) GetGains() float64 { - if x != nil { - return x.Gains - } - return 0 -} - -type PortfolioEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Type PortfolioEventType `protobuf:"varint,2,opt,name=type,proto3,enum=mgo.portfolio.v1.PortfolioEventType" json:"type,omitempty"` - Time *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=time,proto3" json:"time,omitempty"` - PortfolioId string `protobuf:"bytes,4,opt,name=portfolio_id,json=portfolioId,proto3" json:"portfolio_id,omitempty"` - SecurityId string `protobuf:"bytes,5,opt,name=security_id,json=securityId,proto3" json:"security_id,omitempty"` - Amount float64 `protobuf:"fixed64,10,opt,name=amount,proto3" json:"amount,omitempty"` - Price *Currency `protobuf:"bytes,11,opt,name=price,proto3" json:"price,omitempty"` - Fees *Currency `protobuf:"bytes,12,opt,name=fees,proto3" json:"fees,omitempty"` - Taxes *Currency `protobuf:"bytes,13,opt,name=taxes,proto3" json:"taxes,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PortfolioEvent) Reset() { - *x = PortfolioEvent{} - mi := &file_mgo_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PortfolioEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PortfolioEvent) ProtoMessage() {} - -func (x *PortfolioEvent) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[22] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PortfolioEvent.ProtoReflect.Descriptor instead. -func (*PortfolioEvent) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{22} -} - -func (x *PortfolioEvent) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *PortfolioEvent) GetType() PortfolioEventType { - if x != nil { - return x.Type - } - return PortfolioEventType_PORTFOLIO_EVENT_TYPE_UNSPECIFIED -} - -func (x *PortfolioEvent) GetTime() *timestamppb.Timestamp { - if x != nil { - return x.Time - } - return nil -} - -func (x *PortfolioEvent) GetPortfolioId() string { - if x != nil { - return x.PortfolioId - } - return "" -} - -func (x *PortfolioEvent) GetSecurityId() string { - if x != nil { - return x.SecurityId - } - return "" -} - -func (x *PortfolioEvent) GetAmount() float64 { - if x != nil { - return x.Amount - } - return 0 -} - -func (x *PortfolioEvent) GetPrice() *Currency { - if x != nil { - return x.Price - } - return nil -} - -func (x *PortfolioEvent) GetFees() *Currency { - if x != nil { - return x.Fees - } - return nil -} - -func (x *PortfolioEvent) GetTaxes() *Currency { - if x != nil { - return x.Taxes - } - return nil -} - -type Security struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Id contains the unique resource ID. For a stock or bond, this should be - // an ISIN. - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // DisplayName contains the human readable id. - DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - ListedOn []*ListedSecurity `protobuf:"bytes,4,rep,name=listed_on,json=listedOn,proto3" json:"listed_on,omitempty"` - QuoteProvider *string `protobuf:"bytes,10,opt,name=quote_provider,json=quoteProvider,proto3,oneof" json:"quote_provider,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Security) Reset() { - *x = Security{} - mi := &file_mgo_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Security) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Security) ProtoMessage() {} - -func (x *Security) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[23] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Security.ProtoReflect.Descriptor instead. -func (*Security) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{23} -} - -func (x *Security) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Security) GetDisplayName() string { - if x != nil { - return x.DisplayName - } - return "" -} - -func (x *Security) GetListedOn() []*ListedSecurity { - if x != nil { - return x.ListedOn - } - return nil -} - -func (x *Security) GetQuoteProvider() string { - if x != nil && x.QuoteProvider != nil { - return *x.QuoteProvider - } - return "" -} - -type ListedSecurity struct { - state protoimpl.MessageState `protogen:"open.v1"` - SecurityId string `protobuf:"bytes,1,opt,name=security_id,json=securityId,proto3" json:"security_id,omitempty"` - Ticker string `protobuf:"bytes,3,opt,name=ticker,proto3" json:"ticker,omitempty"` - Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"` - LatestQuote *Currency `protobuf:"bytes,5,opt,name=latest_quote,json=latestQuote,proto3,oneof" json:"latest_quote,omitempty"` - LatestQuoteTimestamp *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=latest_quote_timestamp,json=latestQuoteTimestamp,proto3,oneof" json:"latest_quote_timestamp,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListedSecurity) Reset() { - *x = ListedSecurity{} - mi := &file_mgo_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListedSecurity) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListedSecurity) ProtoMessage() {} - -func (x *ListedSecurity) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[24] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListedSecurity.ProtoReflect.Descriptor instead. -func (*ListedSecurity) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{24} -} - -func (x *ListedSecurity) GetSecurityId() string { - if x != nil { - return x.SecurityId - } - return "" -} - -func (x *ListedSecurity) GetTicker() string { - if x != nil { - return x.Ticker - } - return "" -} - -func (x *ListedSecurity) GetCurrency() string { - if x != nil { - return x.Currency - } - return "" -} - -func (x *ListedSecurity) GetLatestQuote() *Currency { - if x != nil { - return x.LatestQuote - } - return nil -} - -func (x *ListedSecurity) GetLatestQuoteTimestamp() *timestamppb.Timestamp { - if x != nil { - return x.LatestQuoteTimestamp - } - return nil -} - -type ListSecuritiesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Filter *ListSecuritiesRequest_Filter `protobuf:"bytes,5,opt,name=filter,proto3,oneof" json:"filter,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSecuritiesRequest) Reset() { - *x = ListSecuritiesRequest{} - mi := &file_mgo_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSecuritiesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSecuritiesRequest) ProtoMessage() {} - -func (x *ListSecuritiesRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[25] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSecuritiesRequest.ProtoReflect.Descriptor instead. -func (*ListSecuritiesRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{25} -} - -func (x *ListSecuritiesRequest) GetFilter() *ListSecuritiesRequest_Filter { - if x != nil { - return x.Filter - } - return nil -} - -type ListSecuritiesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Securities []*Security `protobuf:"bytes,1,rep,name=securities,proto3" json:"securities,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSecuritiesResponse) Reset() { - *x = ListSecuritiesResponse{} - mi := &file_mgo_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSecuritiesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSecuritiesResponse) ProtoMessage() {} - -func (x *ListSecuritiesResponse) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[26] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSecuritiesResponse.ProtoReflect.Descriptor instead. -func (*ListSecuritiesResponse) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{26} -} - -func (x *ListSecuritiesResponse) GetSecurities() []*Security { - if x != nil { - return x.Securities - } - return nil -} - -type GetSecurityRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetSecurityRequest) Reset() { - *x = GetSecurityRequest{} - mi := &file_mgo_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetSecurityRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSecurityRequest) ProtoMessage() {} - -func (x *GetSecurityRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[27] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSecurityRequest.ProtoReflect.Descriptor instead. -func (*GetSecurityRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{27} -} - -func (x *GetSecurityRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type CreateSecurityRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Security *Security `protobuf:"bytes,1,opt,name=security,proto3" json:"security,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreateSecurityRequest) Reset() { - *x = CreateSecurityRequest{} - mi := &file_mgo_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreateSecurityRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateSecurityRequest) ProtoMessage() {} - -func (x *CreateSecurityRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[28] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateSecurityRequest.ProtoReflect.Descriptor instead. -func (*CreateSecurityRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{28} -} - -func (x *CreateSecurityRequest) GetSecurity() *Security { - if x != nil { - return x.Security - } - return nil -} - -type UpdateSecurityRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Security *Security `protobuf:"bytes,1,opt,name=security,proto3" json:"security,omitempty"` - UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=updateMask,proto3" json:"updateMask,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateSecurityRequest) Reset() { - *x = UpdateSecurityRequest{} - mi := &file_mgo_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateSecurityRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateSecurityRequest) ProtoMessage() {} - -func (x *UpdateSecurityRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[29] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateSecurityRequest.ProtoReflect.Descriptor instead. -func (*UpdateSecurityRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{29} -} - -func (x *UpdateSecurityRequest) GetSecurity() *Security { - if x != nil { - return x.Security - } - return nil -} - -func (x *UpdateSecurityRequest) GetUpdateMask() *fieldmaskpb.FieldMask { - if x != nil { - return x.UpdateMask - } - return nil -} - -type DeleteSecurityRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteSecurityRequest) Reset() { - *x = DeleteSecurityRequest{} - mi := &file_mgo_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteSecurityRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteSecurityRequest) ProtoMessage() {} - -func (x *DeleteSecurityRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[30] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteSecurityRequest.ProtoReflect.Descriptor instead. -func (*DeleteSecurityRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{30} -} - -func (x *DeleteSecurityRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type TriggerQuoteUpdateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - SecurityIds []string `protobuf:"bytes,1,rep,name=security_ids,json=securityIds,proto3" json:"security_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TriggerQuoteUpdateRequest) Reset() { - *x = TriggerQuoteUpdateRequest{} - mi := &file_mgo_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TriggerQuoteUpdateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TriggerQuoteUpdateRequest) ProtoMessage() {} - -func (x *TriggerQuoteUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[31] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TriggerQuoteUpdateRequest.ProtoReflect.Descriptor instead. -func (*TriggerQuoteUpdateRequest) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{31} -} - -func (x *TriggerQuoteUpdateRequest) GetSecurityIds() []string { - if x != nil { - return x.SecurityIds - } - return nil -} - -type TriggerQuoteUpdateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TriggerQuoteUpdateResponse) Reset() { - *x = TriggerQuoteUpdateResponse{} - mi := &file_mgo_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TriggerQuoteUpdateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TriggerQuoteUpdateResponse) ProtoMessage() {} - -func (x *TriggerQuoteUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[32] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TriggerQuoteUpdateResponse.ProtoReflect.Descriptor instead. -func (*TriggerQuoteUpdateResponse) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{32} -} - -type ListSecuritiesRequest_Filter struct { - state protoimpl.MessageState `protogen:"open.v1"` - SecurityIds []string `protobuf:"bytes,1,rep,name=security_ids,json=securityIds,proto3" json:"security_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSecuritiesRequest_Filter) Reset() { - *x = ListSecuritiesRequest_Filter{} - mi := &file_mgo_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSecuritiesRequest_Filter) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSecuritiesRequest_Filter) ProtoMessage() {} - -func (x *ListSecuritiesRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_mgo_proto_msgTypes[34] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSecuritiesRequest_Filter.ProtoReflect.Descriptor instead. -func (*ListSecuritiesRequest_Filter) Descriptor() ([]byte, []int) { - return file_mgo_proto_rawDescGZIP(), []int{25, 0} -} - -func (x *ListSecuritiesRequest_Filter) GetSecurityIds() []string { - if x != nil { - return x.SecurityIds - } - return nil -} - -var File_mgo_proto protoreflect.FileDescriptor - -var file_mgo_proto_rawDesc = []byte{ - 0x0a, 0x09, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, - 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, - 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x42, 0x0a, 0x08, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, - 0x22, 0x58, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x09, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x22, 0x17, 0x0a, 0x15, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, - 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x22, - 0x2a, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x22, 0x99, 0x01, 0x0a, 0x16, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x70, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x3f, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x2d, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x22, 0x7a, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x49, 0x64, 0x12, 0x33, 0x0a, - 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x74, 0x69, - 0x6d, 0x65, 0x22, 0x6c, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6d, - 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x35, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4a, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0c, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x49, 0x64, 0x22, 0x6e, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x21, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, - 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, - 0x61, 0x73, 0x6b, 0x22, 0x4f, 0x0a, 0x21, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x63, 0x0a, 0x19, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x08, 0x66, 0x72, 0x6f, - 0x6d, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x07, 0x66, 0x72, 0x6f, 0x6d, 0x43, 0x73, 0x76, 0x22, 0x61, 0x0a, 0x18, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x0c, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x67, - 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x0b, 0x62, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x99, 0x01, 0x0a, - 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x07, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, - 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x07, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x2f, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x50, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0c, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x0f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x0d, 0x62, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x4a, 0x0a, 0x0b, 0x42, - 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x9d, 0x06, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x33, 0x0a, - 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x74, 0x69, - 0x6d, 0x65, 0x12, 0x55, 0x0a, 0x09, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5a, 0x0a, 0x16, 0x66, 0x69, 0x72, - 0x73, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x48, 0x00, 0x52, 0x14, 0x66, 0x69, - 0x72, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, - 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, - 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, - 0x03, 0xe0, 0x41, 0x02, 0x52, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x63, 0x68, - 0x61, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x61, 0x72, 0x6b, - 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x50, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x18, - 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x74, 0x4f, 0x72, 0x4c, 0x6f, 0x73, 0x73, 0x12, 0x24, 0x0a, 0x0b, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x67, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x47, 0x61, 0x69, 0x6e, 0x73, 0x12, - 0x33, 0x0a, 0x04, 0x63, 0x61, 0x73, 0x68, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, - 0x63, 0x61, 0x73, 0x68, 0x12, 0x53, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x17, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, - 0x03, 0xe0, 0x41, 0x02, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x61, 0x0a, 0x0e, 0x50, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 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, 0x39, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6d, - 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x19, 0x0a, 0x17, - 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x22, 0xa7, 0x04, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, - 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x0e, 0x70, 0x75, 0x72, 0x63, 0x68, - 0x61, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x0d, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x46, 0x0a, 0x0e, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0d, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, - 0x73, 0x65, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, - 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x6d, - 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x0b, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, - 0x3e, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, - 0x03, 0xe0, 0x41, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, - 0x45, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x73, - 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x74, - 0x4f, 0x72, 0x4c, 0x6f, 0x73, 0x73, 0x12, 0x19, 0x0a, 0x05, 0x67, 0x61, 0x69, 0x6e, 0x73, 0x18, - 0x15, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x05, 0x67, 0x61, 0x69, 0x6e, - 0x73, 0x22, 0xa7, 0x03, 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, - 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x0a, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x42, 0x03, 0xe0, 0x41, 0x02, - 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, - 0x33, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, - 0x66, 0x65, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x61, 0x78, 0x65, 0x73, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, - 0x03, 0xe0, 0x41, 0x02, 0x52, 0x05, 0x74, 0x61, 0x78, 0x65, 0x73, 0x22, 0xcf, 0x01, 0x0a, 0x08, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, - 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x5f, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, - 0x08, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x12, 0x2f, 0x0a, 0x0e, 0x71, 0x75, 0x6f, - 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x48, 0x00, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x71, - 0x75, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, 0xbb, 0x02, - 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, - 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x74, 0x69, 0x63, - 0x6b, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x71, - 0x75, 0x6f, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, - 0x51, 0x75, 0x6f, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x55, 0x0a, 0x16, 0x6c, 0x61, 0x74, 0x65, - 0x73, 0x74, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x48, 0x01, 0x52, 0x14, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x51, 0x75, - 0x6f, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x88, 0x01, 0x01, 0x42, - 0x0f, 0x0a, 0x0d, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, - 0x42, 0x19, 0x0a, 0x17, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x71, 0x75, 0x6f, 0x74, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x9c, 0x01, 0x0a, 0x15, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, - 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, - 0x01, 0x01, 0x1a, 0x2b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x16, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x75, - 0x72, 0x69, 0x74, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x75, 0x72, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x29, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x75, - 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, - 0x22, 0x54, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65, 0x63, - 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, - 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x08, 0x73, 0x65, - 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x22, 0x95, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x42, 0x03, - 0xe0, 0x41, 0x02, 0x52, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3f, 0x0a, - 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x42, 0x03, 0xe0, - 0x41, 0x02, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x2c, - 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3e, 0x0a, 0x19, - 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x63, - 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0b, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x22, 0x1c, 0x0a, 0x1a, - 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xaf, 0x03, 0x0a, 0x12, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x24, 0x0a, 0x20, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x50, 0x4f, 0x52, 0x54, 0x46, - 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x42, 0x55, 0x59, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, - 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, - 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x29, 0x0a, 0x25, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, - 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4c, - 0x49, 0x56, 0x45, 0x52, 0x59, 0x5f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x12, - 0x2a, 0x0a, 0x26, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, - 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x59, - 0x5f, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x04, 0x12, 0x21, 0x0a, 0x1d, 0x50, - 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, 0x56, 0x49, 0x44, 0x45, 0x4e, 0x44, 0x10, 0x0a, 0x12, 0x21, - 0x0a, 0x1d, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x45, 0x53, 0x54, 0x10, - 0x0b, 0x12, 0x25, 0x0a, 0x21, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x4f, 0x53, 0x49, - 0x54, 0x5f, 0x43, 0x41, 0x53, 0x48, 0x10, 0x14, 0x12, 0x26, 0x0a, 0x22, 0x50, 0x4f, 0x52, 0x54, - 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x57, 0x49, 0x54, 0x48, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x43, 0x41, 0x53, 0x48, 0x10, 0x15, - 0x12, 0x25, 0x0a, 0x21, 0x50, 0x4f, 0x52, 0x54, 0x46, 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, - 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, - 0x5f, 0x46, 0x45, 0x45, 0x53, 0x10, 0x1e, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x4f, 0x52, 0x54, 0x46, - 0x4f, 0x4c, 0x49, 0x4f, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x54, 0x41, 0x58, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x10, 0x1f, 0x32, 0xf2, 0x0e, 0x0a, - 0x10, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x7b, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x22, 0x21, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1b, 0x3a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x22, 0x0e, - 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x12, 0x7e, - 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, - 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, - 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x90, 0x02, 0x01, 0x12, 0x72, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x25, - 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x90, - 0x02, 0x01, 0x12, 0x58, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x53, 0x0a, 0x0f, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, - 0x28, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x9d, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x22, 0x31, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x2f, 0x7b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x90, 0x02, - 0x01, 0x12, 0xc0, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x33, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x4b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x45, 0x3a, - 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x36, 0x2f, 0x76, - 0x31, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x2f, 0x7b, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x8f, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x30, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x90, 0x02, 0x01, 0x12, 0xbb, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x12, 0x2a, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x2f, 0x7b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x90, 0x02, 0x01, 0x12, 0xab, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x30, 0x3a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, - 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2f, 0x7b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x69, - 0x64, 0x7d, 0x12, 0x69, 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x33, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x59, 0x0a, - 0x12, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x5e, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6e, - 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6e, - 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x57, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x2e, - 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x32, 0xfe, 0x04, 0x0a, 0x11, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7e, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x90, 0x02, 0x01, 0x12, 0x6f, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x65, - 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x24, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, - 0x75, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, - 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, - 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x90, 0x02, 0x01, 0x12, 0x55, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, - 0x55, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, - 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x67, 0x6f, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x77, 0x0a, 0x1a, 0x54, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x51, 0x75, 0x6f, 0x74, - 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, - 0x65, 0x72, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x6d, 0x67, 0x6f, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x51, - 0x75, 0x6f, 0x74, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6f, 0x78, 0x69, 0x73, 0x74, 0x6f, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2d, 0x67, 0x6f, - 0x70, 0x68, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x3b, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_mgo_proto_rawDescOnce sync.Once - file_mgo_proto_rawDescData = file_mgo_proto_rawDesc -) - -func file_mgo_proto_rawDescGZIP() []byte { - file_mgo_proto_rawDescOnce.Do(func() { - file_mgo_proto_rawDescData = protoimpl.X.CompressGZIP(file_mgo_proto_rawDescData) - }) - return file_mgo_proto_rawDescData -} - -var file_mgo_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_mgo_proto_msgTypes = make([]protoimpl.MessageInfo, 35) -var file_mgo_proto_goTypes = []any{ - (PortfolioEventType)(0), // 0: mgo.portfolio.v1.PortfolioEventType - (*Currency)(nil), // 1: mgo.portfolio.v1.Currency - (*CreatePortfolioRequest)(nil), // 2: mgo.portfolio.v1.CreatePortfolioRequest - (*ListPortfoliosRequest)(nil), // 3: mgo.portfolio.v1.ListPortfoliosRequest - (*ListPortfoliosResponse)(nil), // 4: mgo.portfolio.v1.ListPortfoliosResponse - (*GetPortfolioRequest)(nil), // 5: mgo.portfolio.v1.GetPortfolioRequest - (*UpdatePortfolioRequest)(nil), // 6: mgo.portfolio.v1.UpdatePortfolioRequest - (*DeletePortfolioRequest)(nil), // 7: mgo.portfolio.v1.DeletePortfolioRequest - (*GetPortfolioSnapshotRequest)(nil), // 8: mgo.portfolio.v1.GetPortfolioSnapshotRequest - (*CreatePortfolioTransactionRequest)(nil), // 9: mgo.portfolio.v1.CreatePortfolioTransactionRequest - (*GetPortfolioTransactionRequest)(nil), // 10: mgo.portfolio.v1.GetPortfolioTransactionRequest - (*ListPortfolioTransactionsRequest)(nil), // 11: mgo.portfolio.v1.ListPortfolioTransactionsRequest - (*ListPortfolioTransactionsResponse)(nil), // 12: mgo.portfolio.v1.ListPortfolioTransactionsResponse - (*UpdatePortfolioTransactionRequest)(nil), // 13: mgo.portfolio.v1.UpdatePortfolioTransactionRequest - (*DeletePortfolioTransactionRequest)(nil), // 14: mgo.portfolio.v1.DeletePortfolioTransactionRequest - (*ImportTransactionsRequest)(nil), // 15: mgo.portfolio.v1.ImportTransactionsRequest - (*CreateBankAccountRequest)(nil), // 16: mgo.portfolio.v1.CreateBankAccountRequest - (*UpdateBankAccountRequest)(nil), // 17: mgo.portfolio.v1.UpdateBankAccountRequest - (*DeleteBankAccountRequest)(nil), // 18: mgo.portfolio.v1.DeleteBankAccountRequest - (*Portfolio)(nil), // 19: mgo.portfolio.v1.Portfolio - (*BankAccount)(nil), // 20: mgo.portfolio.v1.BankAccount - (*PortfolioSnapshot)(nil), // 21: mgo.portfolio.v1.PortfolioSnapshot - (*PortfolioPosition)(nil), // 22: mgo.portfolio.v1.PortfolioPosition - (*PortfolioEvent)(nil), // 23: mgo.portfolio.v1.PortfolioEvent - (*Security)(nil), // 24: mgo.portfolio.v1.Security - (*ListedSecurity)(nil), // 25: mgo.portfolio.v1.ListedSecurity - (*ListSecuritiesRequest)(nil), // 26: mgo.portfolio.v1.ListSecuritiesRequest - (*ListSecuritiesResponse)(nil), // 27: mgo.portfolio.v1.ListSecuritiesResponse - (*GetSecurityRequest)(nil), // 28: mgo.portfolio.v1.GetSecurityRequest - (*CreateSecurityRequest)(nil), // 29: mgo.portfolio.v1.CreateSecurityRequest - (*UpdateSecurityRequest)(nil), // 30: mgo.portfolio.v1.UpdateSecurityRequest - (*DeleteSecurityRequest)(nil), // 31: mgo.portfolio.v1.DeleteSecurityRequest - (*TriggerQuoteUpdateRequest)(nil), // 32: mgo.portfolio.v1.TriggerQuoteUpdateRequest - (*TriggerQuoteUpdateResponse)(nil), // 33: mgo.portfolio.v1.TriggerQuoteUpdateResponse - nil, // 34: mgo.portfolio.v1.PortfolioSnapshot.PositionsEntry - (*ListSecuritiesRequest_Filter)(nil), // 35: mgo.portfolio.v1.ListSecuritiesRequest.Filter - (*fieldmaskpb.FieldMask)(nil), // 36: google.protobuf.FieldMask - (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 38: google.protobuf.Empty -} -var file_mgo_proto_depIdxs = []int32{ - 19, // 0: mgo.portfolio.v1.CreatePortfolioRequest.portfolio:type_name -> mgo.portfolio.v1.Portfolio - 19, // 1: mgo.portfolio.v1.ListPortfoliosResponse.portfolios:type_name -> mgo.portfolio.v1.Portfolio - 19, // 2: mgo.portfolio.v1.UpdatePortfolioRequest.portfolio:type_name -> mgo.portfolio.v1.Portfolio - 36, // 3: mgo.portfolio.v1.UpdatePortfolioRequest.updateMask:type_name -> google.protobuf.FieldMask - 37, // 4: mgo.portfolio.v1.GetPortfolioSnapshotRequest.time:type_name -> google.protobuf.Timestamp - 23, // 5: mgo.portfolio.v1.CreatePortfolioTransactionRequest.transaction:type_name -> mgo.portfolio.v1.PortfolioEvent - 23, // 6: mgo.portfolio.v1.ListPortfolioTransactionsResponse.transactions:type_name -> mgo.portfolio.v1.PortfolioEvent - 23, // 7: mgo.portfolio.v1.UpdatePortfolioTransactionRequest.transaction:type_name -> mgo.portfolio.v1.PortfolioEvent - 36, // 8: mgo.portfolio.v1.UpdatePortfolioTransactionRequest.updateMask:type_name -> google.protobuf.FieldMask - 20, // 9: mgo.portfolio.v1.CreateBankAccountRequest.bank_account:type_name -> mgo.portfolio.v1.BankAccount - 20, // 10: mgo.portfolio.v1.UpdateBankAccountRequest.account:type_name -> mgo.portfolio.v1.BankAccount - 36, // 11: mgo.portfolio.v1.UpdateBankAccountRequest.updateMask:type_name -> google.protobuf.FieldMask - 23, // 12: mgo.portfolio.v1.Portfolio.events:type_name -> mgo.portfolio.v1.PortfolioEvent - 37, // 13: mgo.portfolio.v1.PortfolioSnapshot.time:type_name -> google.protobuf.Timestamp - 34, // 14: mgo.portfolio.v1.PortfolioSnapshot.positions:type_name -> mgo.portfolio.v1.PortfolioSnapshot.PositionsEntry - 37, // 15: mgo.portfolio.v1.PortfolioSnapshot.first_transaction_time:type_name -> google.protobuf.Timestamp - 1, // 16: mgo.portfolio.v1.PortfolioSnapshot.total_purchase_value:type_name -> mgo.portfolio.v1.Currency - 1, // 17: mgo.portfolio.v1.PortfolioSnapshot.total_market_value:type_name -> mgo.portfolio.v1.Currency - 1, // 18: mgo.portfolio.v1.PortfolioSnapshot.total_profit_or_loss:type_name -> mgo.portfolio.v1.Currency - 1, // 19: mgo.portfolio.v1.PortfolioSnapshot.cash:type_name -> mgo.portfolio.v1.Currency - 1, // 20: mgo.portfolio.v1.PortfolioSnapshot.total_portfolio_value:type_name -> mgo.portfolio.v1.Currency - 24, // 21: mgo.portfolio.v1.PortfolioPosition.security:type_name -> mgo.portfolio.v1.Security - 1, // 22: mgo.portfolio.v1.PortfolioPosition.purchase_value:type_name -> mgo.portfolio.v1.Currency - 1, // 23: mgo.portfolio.v1.PortfolioPosition.purchase_price:type_name -> mgo.portfolio.v1.Currency - 1, // 24: mgo.portfolio.v1.PortfolioPosition.market_value:type_name -> mgo.portfolio.v1.Currency - 1, // 25: mgo.portfolio.v1.PortfolioPosition.market_price:type_name -> mgo.portfolio.v1.Currency - 1, // 26: mgo.portfolio.v1.PortfolioPosition.total_fees:type_name -> mgo.portfolio.v1.Currency - 1, // 27: mgo.portfolio.v1.PortfolioPosition.profit_or_loss:type_name -> mgo.portfolio.v1.Currency - 0, // 28: mgo.portfolio.v1.PortfolioEvent.type:type_name -> mgo.portfolio.v1.PortfolioEventType - 37, // 29: mgo.portfolio.v1.PortfolioEvent.time:type_name -> google.protobuf.Timestamp - 1, // 30: mgo.portfolio.v1.PortfolioEvent.price:type_name -> mgo.portfolio.v1.Currency - 1, // 31: mgo.portfolio.v1.PortfolioEvent.fees:type_name -> mgo.portfolio.v1.Currency - 1, // 32: mgo.portfolio.v1.PortfolioEvent.taxes:type_name -> mgo.portfolio.v1.Currency - 25, // 33: mgo.portfolio.v1.Security.listed_on:type_name -> mgo.portfolio.v1.ListedSecurity - 1, // 34: mgo.portfolio.v1.ListedSecurity.latest_quote:type_name -> mgo.portfolio.v1.Currency - 37, // 35: mgo.portfolio.v1.ListedSecurity.latest_quote_timestamp:type_name -> google.protobuf.Timestamp - 35, // 36: mgo.portfolio.v1.ListSecuritiesRequest.filter:type_name -> mgo.portfolio.v1.ListSecuritiesRequest.Filter - 24, // 37: mgo.portfolio.v1.ListSecuritiesResponse.securities:type_name -> mgo.portfolio.v1.Security - 24, // 38: mgo.portfolio.v1.CreateSecurityRequest.security:type_name -> mgo.portfolio.v1.Security - 24, // 39: mgo.portfolio.v1.UpdateSecurityRequest.security:type_name -> mgo.portfolio.v1.Security - 36, // 40: mgo.portfolio.v1.UpdateSecurityRequest.updateMask:type_name -> google.protobuf.FieldMask - 22, // 41: mgo.portfolio.v1.PortfolioSnapshot.PositionsEntry.value:type_name -> mgo.portfolio.v1.PortfolioPosition - 2, // 42: mgo.portfolio.v1.PortfolioService.CreatePortfolio:input_type -> mgo.portfolio.v1.CreatePortfolioRequest - 3, // 43: mgo.portfolio.v1.PortfolioService.ListPortfolios:input_type -> mgo.portfolio.v1.ListPortfoliosRequest - 5, // 44: mgo.portfolio.v1.PortfolioService.GetPortfolio:input_type -> mgo.portfolio.v1.GetPortfolioRequest - 6, // 45: mgo.portfolio.v1.PortfolioService.UpdatePortfolio:input_type -> mgo.portfolio.v1.UpdatePortfolioRequest - 7, // 46: mgo.portfolio.v1.PortfolioService.DeletePortfolio:input_type -> mgo.portfolio.v1.DeletePortfolioRequest - 8, // 47: mgo.portfolio.v1.PortfolioService.GetPortfolioSnapshot:input_type -> mgo.portfolio.v1.GetPortfolioSnapshotRequest - 9, // 48: mgo.portfolio.v1.PortfolioService.CreatePortfolioTransaction:input_type -> mgo.portfolio.v1.CreatePortfolioTransactionRequest - 10, // 49: mgo.portfolio.v1.PortfolioService.GetPortfolioTransaction:input_type -> mgo.portfolio.v1.GetPortfolioTransactionRequest - 11, // 50: mgo.portfolio.v1.PortfolioService.ListPortfolioTransactions:input_type -> mgo.portfolio.v1.ListPortfolioTransactionsRequest - 13, // 51: mgo.portfolio.v1.PortfolioService.UpdatePortfolioTransaction:input_type -> mgo.portfolio.v1.UpdatePortfolioTransactionRequest - 14, // 52: mgo.portfolio.v1.PortfolioService.DeletePortfolioTransaction:input_type -> mgo.portfolio.v1.DeletePortfolioTransactionRequest - 15, // 53: mgo.portfolio.v1.PortfolioService.ImportTransactions:input_type -> mgo.portfolio.v1.ImportTransactionsRequest - 16, // 54: mgo.portfolio.v1.PortfolioService.CreateBankAccount:input_type -> mgo.portfolio.v1.CreateBankAccountRequest - 17, // 55: mgo.portfolio.v1.PortfolioService.UpdateBankAccount:input_type -> mgo.portfolio.v1.UpdateBankAccountRequest - 18, // 56: mgo.portfolio.v1.PortfolioService.DeleteBankAccount:input_type -> mgo.portfolio.v1.DeleteBankAccountRequest - 26, // 57: mgo.portfolio.v1.SecuritiesService.ListSecurities:input_type -> mgo.portfolio.v1.ListSecuritiesRequest - 28, // 58: mgo.portfolio.v1.SecuritiesService.GetSecurity:input_type -> mgo.portfolio.v1.GetSecurityRequest - 29, // 59: mgo.portfolio.v1.SecuritiesService.CreateSecurity:input_type -> mgo.portfolio.v1.CreateSecurityRequest - 30, // 60: mgo.portfolio.v1.SecuritiesService.UpdateSecurity:input_type -> mgo.portfolio.v1.UpdateSecurityRequest - 31, // 61: mgo.portfolio.v1.SecuritiesService.DeleteSecurity:input_type -> mgo.portfolio.v1.DeleteSecurityRequest - 32, // 62: mgo.portfolio.v1.SecuritiesService.TriggerSecurityQuoteUpdate:input_type -> mgo.portfolio.v1.TriggerQuoteUpdateRequest - 19, // 63: mgo.portfolio.v1.PortfolioService.CreatePortfolio:output_type -> mgo.portfolio.v1.Portfolio - 4, // 64: mgo.portfolio.v1.PortfolioService.ListPortfolios:output_type -> mgo.portfolio.v1.ListPortfoliosResponse - 19, // 65: mgo.portfolio.v1.PortfolioService.GetPortfolio:output_type -> mgo.portfolio.v1.Portfolio - 19, // 66: mgo.portfolio.v1.PortfolioService.UpdatePortfolio:output_type -> mgo.portfolio.v1.Portfolio - 38, // 67: mgo.portfolio.v1.PortfolioService.DeletePortfolio:output_type -> google.protobuf.Empty - 21, // 68: mgo.portfolio.v1.PortfolioService.GetPortfolioSnapshot:output_type -> mgo.portfolio.v1.PortfolioSnapshot - 23, // 69: mgo.portfolio.v1.PortfolioService.CreatePortfolioTransaction:output_type -> mgo.portfolio.v1.PortfolioEvent - 23, // 70: mgo.portfolio.v1.PortfolioService.GetPortfolioTransaction:output_type -> mgo.portfolio.v1.PortfolioEvent - 12, // 71: mgo.portfolio.v1.PortfolioService.ListPortfolioTransactions:output_type -> mgo.portfolio.v1.ListPortfolioTransactionsResponse - 23, // 72: mgo.portfolio.v1.PortfolioService.UpdatePortfolioTransaction:output_type -> mgo.portfolio.v1.PortfolioEvent - 38, // 73: mgo.portfolio.v1.PortfolioService.DeletePortfolioTransaction:output_type -> google.protobuf.Empty - 38, // 74: mgo.portfolio.v1.PortfolioService.ImportTransactions:output_type -> google.protobuf.Empty - 20, // 75: mgo.portfolio.v1.PortfolioService.CreateBankAccount:output_type -> mgo.portfolio.v1.BankAccount - 20, // 76: mgo.portfolio.v1.PortfolioService.UpdateBankAccount:output_type -> mgo.portfolio.v1.BankAccount - 38, // 77: mgo.portfolio.v1.PortfolioService.DeleteBankAccount:output_type -> google.protobuf.Empty - 27, // 78: mgo.portfolio.v1.SecuritiesService.ListSecurities:output_type -> mgo.portfolio.v1.ListSecuritiesResponse - 24, // 79: mgo.portfolio.v1.SecuritiesService.GetSecurity:output_type -> mgo.portfolio.v1.Security - 24, // 80: mgo.portfolio.v1.SecuritiesService.CreateSecurity:output_type -> mgo.portfolio.v1.Security - 24, // 81: mgo.portfolio.v1.SecuritiesService.UpdateSecurity:output_type -> mgo.portfolio.v1.Security - 38, // 82: mgo.portfolio.v1.SecuritiesService.DeleteSecurity:output_type -> google.protobuf.Empty - 33, // 83: mgo.portfolio.v1.SecuritiesService.TriggerSecurityQuoteUpdate:output_type -> mgo.portfolio.v1.TriggerQuoteUpdateResponse - 63, // [63:84] is the sub-list for method output_type - 42, // [42:63] is the sub-list for method input_type - 42, // [42:42] is the sub-list for extension type_name - 42, // [42:42] is the sub-list for extension extendee - 0, // [0:42] is the sub-list for field type_name -} - -func init() { file_mgo_proto_init() } -func file_mgo_proto_init() { - if File_mgo_proto != nil { - return - } - file_mgo_proto_msgTypes[20].OneofWrappers = []any{} - file_mgo_proto_msgTypes[23].OneofWrappers = []any{} - file_mgo_proto_msgTypes[24].OneofWrappers = []any{} - file_mgo_proto_msgTypes[25].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_mgo_proto_rawDesc, - NumEnums: 1, - NumMessages: 35, - NumExtensions: 0, - NumServices: 2, - }, - GoTypes: file_mgo_proto_goTypes, - DependencyIndexes: file_mgo_proto_depIdxs, - EnumInfos: file_mgo_proto_enumTypes, - MessageInfos: file_mgo_proto_msgTypes, - }.Build() - File_mgo_proto = out.File - file_mgo_proto_rawDesc = nil - file_mgo_proto_goTypes = nil - file_mgo_proto_depIdxs = nil -} diff --git a/gen/portfolio.go b/gen/portfolio.go deleted file mode 100644 index 110e60b4..00000000 --- a/gen/portfolio.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "hash/fnv" - "log/slog" - "strconv" - "time" -) - -func (p *Portfolio) EventMap() (m map[string][]*PortfolioEvent) { - m = make(map[string][]*PortfolioEvent) - - for _, tx := range p.Events { - name := tx.GetSecurityId() - if name != "" { - m[name] = append(m[name], tx) - } else { - // a little bit of a hack - m["cash"] = append(m["cash"], tx) - } - } - - return -} - -func EventsBefore(txs []*PortfolioEvent, t time.Time) (out []*PortfolioEvent) { - out = make([]*PortfolioEvent, 0, len(txs)) - - for _, tx := range txs { - if tx.GetTime().AsTime().After(t) { - continue - } - - out = append(out, tx) - } - - return -} - -func (tx *PortfolioEvent) MakeUniqueID() { - // Create a unique ID based on a hash containing: - // - security ID - // - portfolio ID - // - date - // - amount - h := fnv.New64a() - h.Write([]byte(tx.SecurityId)) - h.Write([]byte(tx.PortfolioId)) - h.Write([]byte(tx.Time.AsTime().Local().Format(time.DateTime))) - h.Write([]byte(strconv.FormatInt(int64(tx.Type), 10))) - h.Write([]byte(strconv.FormatInt(int64(tx.Amount), 10))) - - tx.Id = strconv.FormatUint(h.Sum64(), 16) -} - -// LogValue implements slog.LogValuer. -func (tx *PortfolioEvent) LogValue() slog.Value { - return slog.GroupValue( - slog.String("id", tx.Id), - slog.String("security.id", tx.SecurityId), - slog.Float64("amount", float64(tx.Amount)), - slog.String("price", tx.Price.Pretty()), - slog.String("fees", tx.Fees.Pretty()), - slog.String("taxes", tx.Taxes.Pretty()), - ) -} - -// LogValue implements slog.LogValuer. -func (ls *ListedSecurity) LogValue() slog.Value { - return slog.GroupValue( - slog.String("id", ls.SecurityId), - slog.String("ticker", ls.Ticker), - ) -} diff --git a/gen/portfolio_sql.go b/gen/portfolio_sql.go deleted file mode 100644 index 0daa427e..00000000 --- a/gen/portfolio_sql.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "database/sql" - "errors" - "strings" - "time" - - "github.com/oxisto/money-gopher/persistence" - - timestamppb "google.golang.org/protobuf/types/known/timestamppb" -) - -var _ persistence.StorageObject = &Portfolio{} - -func (*Portfolio) InitTables(db *persistence.DB) (err error) { - _, err1 := db.Exec(`CREATE TABLE IF NOT EXISTS portfolios ( -id TEXT PRIMARY KEY, -display_name TEXT NOT NULL -);`) - err2 := (&PortfolioEvent{}).InitTables(db) - - return errors.Join(err1, err2) -} - -func (*Portfolio) PrepareReplace(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`REPLACE INTO portfolios (id, display_name) VALUES (?,?);`) -} - -func (*Portfolio) PrepareList(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name FROM portfolios`) -} - -func (*Portfolio) PrepareGet(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name FROM portfolios WHERE id = ?`) -} - -func (*Portfolio) PrepareUpdate(db *persistence.DB, columns []string) (stmt *sql.Stmt, err error) { - // We need to make sure to quote columns here because they are potentially evil user input - var ( - query string - set []string - ) - - set = make([]string, len(columns)) - for i, col := range columns { - set[i] = persistence.Quote(col) + " = ?" - } - - query += "UPDATE portfolios SET " + strings.Join(set, ", ") + " WHERE id = ?;" - - return db.Prepare(query) -} - -func (*Portfolio) PrepareDelete(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`DELETE FROM portfolios WHERE id = ?`) -} - -func (p *Portfolio) ReplaceIntoArgs() []any { - return []any{p.Id, p.DisplayName} -} - -func (p *Portfolio) UpdateArgs(columns []string) (args []any) { - for _, col := range columns { - switch col { - case "id": - args = append(args, p.Id) - case "display_name": - args = append(args, p.DisplayName) - } - } - - return args -} - -func (*Portfolio) Scan(sc persistence.Scanner) (obj persistence.StorageObject, err error) { - var ( - p Portfolio - ) - - err = sc.Scan(&p.Id, &p.DisplayName) - if err != nil { - return nil, err - } - - return &p, nil -} - -func (*PortfolioEvent) InitTables(db *persistence.DB) (err error) { - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS portfolio_events ( -id TEXT PRIMARY KEY, -type INTEGER NOT NULL, -time DATETIME NOT NULL, -portfolio_id TEXT NOT NULL, -security_id TEXT NOT NULL, -amount REAL, -price INTEGER, -fees INTEGER, -taxes INTEGER -);`) - if err != nil { - return err - } - - return -} - -func (*PortfolioEvent) PrepareReplace(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`REPLACE INTO portfolio_events -(id, type, time, portfolio_id, security_id, amount, price, fees, taxes) -VALUES (?,?,?,?,?,?,?,?,?);`) -} - -func (*PortfolioEvent) PrepareList(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, type, time, portfolio_id, security_id, amount, price, fees, taxes -FROM portfolio_events WHERE portfolio_id = ? ORDER BY time ASC`) -} - -func (*PortfolioEvent) PrepareGet(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT * FROM portfolio_events WHERE id = ?`) -} - -func (*PortfolioEvent) PrepareUpdate(db *persistence.DB, columns []string) (stmt *sql.Stmt, err error) { - // We need to make sure to quote columns here because they are potentially evil user input - var ( - query string - set []string - ) - - set = make([]string, len(columns)) - for i, col := range columns { - set[i] = persistence.Quote(col) + " = ?" - } - - query += "UPDATE portfolio_events SET " + strings.Join(set, ", ") + " WHERE id = ?;" - - return db.Prepare(query) -} - -func (*PortfolioEvent) PrepareDelete(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`DELETE FROM portfolio_events WHERE id = ?`) -} - -func (e *PortfolioEvent) ReplaceIntoArgs() []any { - return []any{ - e.Id, - e.Type, - e.Time.AsTime(), - e.PortfolioId, - e.SecurityId, - e.Amount, - e.Price.GetValue(), - e.Fees.GetValue(), - e.Taxes.GetValue(), - } -} - -func (e *PortfolioEvent) UpdateArgs(columns []string) (args []any) { - for _, col := range columns { - switch col { - case "id": - args = append(args, e.Id) - case "type": - args = append(args, e.Type) - case "time": - args = append(args, e.Time.AsTime()) - case "portfolio_id": - args = append(args, e.PortfolioId) - case "security_id": - args = append(args, e.SecurityId) - case "amount": - args = append(args, e.Amount) - case "price": - args = append(args, e.Price.GetValue()) - case "fees": - args = append(args, e.Fees.GetValue()) - case "taxes": - args = append(args, e.Taxes.GetValue()) - } - } - - return args -} - -func (*PortfolioEvent) Scan(sc persistence.Scanner) (obj persistence.StorageObject, err error) { - var ( - e PortfolioEvent - t time.Time - ) - - e.Price = Zero() - e.Fees = Zero() - e.Taxes = Zero() - - err = sc.Scan( - &e.Id, - &e.Type, - &t, - &e.PortfolioId, - &e.SecurityId, - &e.Amount, - &e.Price.Value, - &e.Fees.Value, - &e.Taxes.Value, - ) - if err != nil { - return nil, err - } - - e.Time = timestamppb.New(t) - - return &e, nil -} diff --git a/gen/portfolio_test.go b/gen/portfolio_test.go deleted file mode 100644 index f351892a..00000000 --- a/gen/portfolio_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "testing" - "time" - - "github.com/oxisto/assert" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func TestPortfolioEvent_MakeUniqueName(t *testing.T) { - type fields struct { - Name string - Type PortfolioEventType - Time *timestamppb.Timestamp - PortfolioName string - SecurityId string - Amount float64 - Price *Currency - Fees *Currency - Taxes *Currency - } - tests := []struct { - name string - fields fields - want assert.Want[*PortfolioEvent] - }{ - { - name: "happy path", - fields: fields{ - SecurityId: "stock", - PortfolioName: "mybank-myportfolio", - Amount: 10, - Type: PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - Time: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local)), - }, - want: func(t *testing.T, tx *PortfolioEvent) bool { - return assert.Equals(t, "a04f32c39c6b9086", tx.Id) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tx := &PortfolioEvent{ - Id: tt.fields.Name, - Type: tt.fields.Type, - Time: tt.fields.Time, - PortfolioId: tt.fields.PortfolioName, - SecurityId: tt.fields.SecurityId, - Amount: tt.fields.Amount, - Price: tt.fields.Price, - Fees: tt.fields.Fees, - Taxes: tt.fields.Taxes, - } - tx.MakeUniqueID() - tt.want(t, tx) - }) - } -} diff --git a/gen/portfoliov1connect/mgo.connect.go b/gen/portfoliov1connect/mgo.connect.go deleted file mode 100644 index b0c39b9e..00000000 --- a/gen/portfoliov1connect/mgo.connect.go +++ /dev/null @@ -1,773 +0,0 @@ -// Code generated by protoc-gen-connect-go. DO NOT EDIT. -// -// Source: mgo.proto - -package portfoliov1connect - -import ( - connect "connectrpc.com/connect" - context "context" - errors "errors" - gen "github.com/oxisto/money-gopher/gen" - emptypb "google.golang.org/protobuf/types/known/emptypb" - http "net/http" - strings "strings" -) - -// This is a compile-time assertion to ensure that this generated file and the connect package are -// compatible. If you get a compiler error that this constant is not defined, this code was -// generated with a version of connect newer than the one compiled into your binary. You can fix the -// problem by either regenerating this code with an older version of connect or updating the connect -// version compiled into your binary. -const _ = connect.IsAtLeastVersion1_13_0 - -const ( - // PortfolioServiceName is the fully-qualified name of the PortfolioService service. - PortfolioServiceName = "mgo.portfolio.v1.PortfolioService" - // SecuritiesServiceName is the fully-qualified name of the SecuritiesService service. - SecuritiesServiceName = "mgo.portfolio.v1.SecuritiesService" -) - -// These constants are the fully-qualified names of the RPCs defined in this package. They're -// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. -// -// Note that these are different from the fully-qualified method names used by -// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to -// reflection-formatted method names, remove the leading slash and convert the remaining slash to a -// period. -const ( - // PortfolioServiceCreatePortfolioProcedure is the fully-qualified name of the PortfolioService's - // CreatePortfolio RPC. - PortfolioServiceCreatePortfolioProcedure = "/mgo.portfolio.v1.PortfolioService/CreatePortfolio" - // PortfolioServiceListPortfoliosProcedure is the fully-qualified name of the PortfolioService's - // ListPortfolios RPC. - PortfolioServiceListPortfoliosProcedure = "/mgo.portfolio.v1.PortfolioService/ListPortfolios" - // PortfolioServiceGetPortfolioProcedure is the fully-qualified name of the PortfolioService's - // GetPortfolio RPC. - PortfolioServiceGetPortfolioProcedure = "/mgo.portfolio.v1.PortfolioService/GetPortfolio" - // PortfolioServiceUpdatePortfolioProcedure is the fully-qualified name of the PortfolioService's - // UpdatePortfolio RPC. - PortfolioServiceUpdatePortfolioProcedure = "/mgo.portfolio.v1.PortfolioService/UpdatePortfolio" - // PortfolioServiceDeletePortfolioProcedure is the fully-qualified name of the PortfolioService's - // DeletePortfolio RPC. - PortfolioServiceDeletePortfolioProcedure = "/mgo.portfolio.v1.PortfolioService/DeletePortfolio" - // PortfolioServiceGetPortfolioSnapshotProcedure is the fully-qualified name of the - // PortfolioService's GetPortfolioSnapshot RPC. - PortfolioServiceGetPortfolioSnapshotProcedure = "/mgo.portfolio.v1.PortfolioService/GetPortfolioSnapshot" - // PortfolioServiceCreatePortfolioTransactionProcedure is the fully-qualified name of the - // PortfolioService's CreatePortfolioTransaction RPC. - PortfolioServiceCreatePortfolioTransactionProcedure = "/mgo.portfolio.v1.PortfolioService/CreatePortfolioTransaction" - // PortfolioServiceGetPortfolioTransactionProcedure is the fully-qualified name of the - // PortfolioService's GetPortfolioTransaction RPC. - PortfolioServiceGetPortfolioTransactionProcedure = "/mgo.portfolio.v1.PortfolioService/GetPortfolioTransaction" - // PortfolioServiceListPortfolioTransactionsProcedure is the fully-qualified name of the - // PortfolioService's ListPortfolioTransactions RPC. - PortfolioServiceListPortfolioTransactionsProcedure = "/mgo.portfolio.v1.PortfolioService/ListPortfolioTransactions" - // PortfolioServiceUpdatePortfolioTransactionProcedure is the fully-qualified name of the - // PortfolioService's UpdatePortfolioTransaction RPC. - PortfolioServiceUpdatePortfolioTransactionProcedure = "/mgo.portfolio.v1.PortfolioService/UpdatePortfolioTransaction" - // PortfolioServiceDeletePortfolioTransactionProcedure is the fully-qualified name of the - // PortfolioService's DeletePortfolioTransaction RPC. - PortfolioServiceDeletePortfolioTransactionProcedure = "/mgo.portfolio.v1.PortfolioService/DeletePortfolioTransaction" - // PortfolioServiceImportTransactionsProcedure is the fully-qualified name of the PortfolioService's - // ImportTransactions RPC. - PortfolioServiceImportTransactionsProcedure = "/mgo.portfolio.v1.PortfolioService/ImportTransactions" - // PortfolioServiceCreateBankAccountProcedure is the fully-qualified name of the PortfolioService's - // CreateBankAccount RPC. - PortfolioServiceCreateBankAccountProcedure = "/mgo.portfolio.v1.PortfolioService/CreateBankAccount" - // PortfolioServiceUpdateBankAccountProcedure is the fully-qualified name of the PortfolioService's - // UpdateBankAccount RPC. - PortfolioServiceUpdateBankAccountProcedure = "/mgo.portfolio.v1.PortfolioService/UpdateBankAccount" - // PortfolioServiceDeleteBankAccountProcedure is the fully-qualified name of the PortfolioService's - // DeleteBankAccount RPC. - PortfolioServiceDeleteBankAccountProcedure = "/mgo.portfolio.v1.PortfolioService/DeleteBankAccount" - // SecuritiesServiceListSecuritiesProcedure is the fully-qualified name of the SecuritiesService's - // ListSecurities RPC. - SecuritiesServiceListSecuritiesProcedure = "/mgo.portfolio.v1.SecuritiesService/ListSecurities" - // SecuritiesServiceGetSecurityProcedure is the fully-qualified name of the SecuritiesService's - // GetSecurity RPC. - SecuritiesServiceGetSecurityProcedure = "/mgo.portfolio.v1.SecuritiesService/GetSecurity" - // SecuritiesServiceCreateSecurityProcedure is the fully-qualified name of the SecuritiesService's - // CreateSecurity RPC. - SecuritiesServiceCreateSecurityProcedure = "/mgo.portfolio.v1.SecuritiesService/CreateSecurity" - // SecuritiesServiceUpdateSecurityProcedure is the fully-qualified name of the SecuritiesService's - // UpdateSecurity RPC. - SecuritiesServiceUpdateSecurityProcedure = "/mgo.portfolio.v1.SecuritiesService/UpdateSecurity" - // SecuritiesServiceDeleteSecurityProcedure is the fully-qualified name of the SecuritiesService's - // DeleteSecurity RPC. - SecuritiesServiceDeleteSecurityProcedure = "/mgo.portfolio.v1.SecuritiesService/DeleteSecurity" - // SecuritiesServiceTriggerSecurityQuoteUpdateProcedure is the fully-qualified name of the - // SecuritiesService's TriggerSecurityQuoteUpdate RPC. - SecuritiesServiceTriggerSecurityQuoteUpdateProcedure = "/mgo.portfolio.v1.SecuritiesService/TriggerSecurityQuoteUpdate" -) - -// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. -var ( - portfolioServiceServiceDescriptor = gen.File_mgo_proto.Services().ByName("PortfolioService") - portfolioServiceCreatePortfolioMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("CreatePortfolio") - portfolioServiceListPortfoliosMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("ListPortfolios") - portfolioServiceGetPortfolioMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("GetPortfolio") - portfolioServiceUpdatePortfolioMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("UpdatePortfolio") - portfolioServiceDeletePortfolioMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("DeletePortfolio") - portfolioServiceGetPortfolioSnapshotMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("GetPortfolioSnapshot") - portfolioServiceCreatePortfolioTransactionMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("CreatePortfolioTransaction") - portfolioServiceGetPortfolioTransactionMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("GetPortfolioTransaction") - portfolioServiceListPortfolioTransactionsMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("ListPortfolioTransactions") - portfolioServiceUpdatePortfolioTransactionMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("UpdatePortfolioTransaction") - portfolioServiceDeletePortfolioTransactionMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("DeletePortfolioTransaction") - portfolioServiceImportTransactionsMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("ImportTransactions") - portfolioServiceCreateBankAccountMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("CreateBankAccount") - portfolioServiceUpdateBankAccountMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("UpdateBankAccount") - portfolioServiceDeleteBankAccountMethodDescriptor = portfolioServiceServiceDescriptor.Methods().ByName("DeleteBankAccount") - securitiesServiceServiceDescriptor = gen.File_mgo_proto.Services().ByName("SecuritiesService") - securitiesServiceListSecuritiesMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("ListSecurities") - securitiesServiceGetSecurityMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("GetSecurity") - securitiesServiceCreateSecurityMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("CreateSecurity") - securitiesServiceUpdateSecurityMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("UpdateSecurity") - securitiesServiceDeleteSecurityMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("DeleteSecurity") - securitiesServiceTriggerSecurityQuoteUpdateMethodDescriptor = securitiesServiceServiceDescriptor.Methods().ByName("TriggerSecurityQuoteUpdate") -) - -// PortfolioServiceClient is a client for the mgo.portfolio.v1.PortfolioService service. -type PortfolioServiceClient interface { - CreatePortfolio(context.Context, *connect.Request[gen.CreatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) - ListPortfolios(context.Context, *connect.Request[gen.ListPortfoliosRequest]) (*connect.Response[gen.ListPortfoliosResponse], error) - GetPortfolio(context.Context, *connect.Request[gen.GetPortfolioRequest]) (*connect.Response[gen.Portfolio], error) - UpdatePortfolio(context.Context, *connect.Request[gen.UpdatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) - DeletePortfolio(context.Context, *connect.Request[gen.DeletePortfolioRequest]) (*connect.Response[emptypb.Empty], error) - GetPortfolioSnapshot(context.Context, *connect.Request[gen.GetPortfolioSnapshotRequest]) (*connect.Response[gen.PortfolioSnapshot], error) - CreatePortfolioTransaction(context.Context, *connect.Request[gen.CreatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - GetPortfolioTransaction(context.Context, *connect.Request[gen.GetPortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - ListPortfolioTransactions(context.Context, *connect.Request[gen.ListPortfolioTransactionsRequest]) (*connect.Response[gen.ListPortfolioTransactionsResponse], error) - UpdatePortfolioTransaction(context.Context, *connect.Request[gen.UpdatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - DeletePortfolioTransaction(context.Context, *connect.Request[gen.DeletePortfolioTransactionRequest]) (*connect.Response[emptypb.Empty], error) - ImportTransactions(context.Context, *connect.Request[gen.ImportTransactionsRequest]) (*connect.Response[emptypb.Empty], error) - CreateBankAccount(context.Context, *connect.Request[gen.CreateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) - UpdateBankAccount(context.Context, *connect.Request[gen.UpdateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) - DeleteBankAccount(context.Context, *connect.Request[gen.DeleteBankAccountRequest]) (*connect.Response[emptypb.Empty], error) -} - -// NewPortfolioServiceClient constructs a client for the mgo.portfolio.v1.PortfolioService service. -// By default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped -// responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the -// connect.WithGRPC() or connect.WithGRPCWeb() options. -// -// The URL supplied here should be the base URL for the Connect or gRPC server (for example, -// http://api.acme.com or https://acme.com/grpc). -func NewPortfolioServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PortfolioServiceClient { - baseURL = strings.TrimRight(baseURL, "/") - return &portfolioServiceClient{ - createPortfolio: connect.NewClient[gen.CreatePortfolioRequest, gen.Portfolio]( - httpClient, - baseURL+PortfolioServiceCreatePortfolioProcedure, - connect.WithSchema(portfolioServiceCreatePortfolioMethodDescriptor), - connect.WithClientOptions(opts...), - ), - listPortfolios: connect.NewClient[gen.ListPortfoliosRequest, gen.ListPortfoliosResponse]( - httpClient, - baseURL+PortfolioServiceListPortfoliosProcedure, - connect.WithSchema(portfolioServiceListPortfoliosMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - getPortfolio: connect.NewClient[gen.GetPortfolioRequest, gen.Portfolio]( - httpClient, - baseURL+PortfolioServiceGetPortfolioProcedure, - connect.WithSchema(portfolioServiceGetPortfolioMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - updatePortfolio: connect.NewClient[gen.UpdatePortfolioRequest, gen.Portfolio]( - httpClient, - baseURL+PortfolioServiceUpdatePortfolioProcedure, - connect.WithSchema(portfolioServiceUpdatePortfolioMethodDescriptor), - connect.WithClientOptions(opts...), - ), - deletePortfolio: connect.NewClient[gen.DeletePortfolioRequest, emptypb.Empty]( - httpClient, - baseURL+PortfolioServiceDeletePortfolioProcedure, - connect.WithSchema(portfolioServiceDeletePortfolioMethodDescriptor), - connect.WithClientOptions(opts...), - ), - getPortfolioSnapshot: connect.NewClient[gen.GetPortfolioSnapshotRequest, gen.PortfolioSnapshot]( - httpClient, - baseURL+PortfolioServiceGetPortfolioSnapshotProcedure, - connect.WithSchema(portfolioServiceGetPortfolioSnapshotMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - createPortfolioTransaction: connect.NewClient[gen.CreatePortfolioTransactionRequest, gen.PortfolioEvent]( - httpClient, - baseURL+PortfolioServiceCreatePortfolioTransactionProcedure, - connect.WithSchema(portfolioServiceCreatePortfolioTransactionMethodDescriptor), - connect.WithClientOptions(opts...), - ), - getPortfolioTransaction: connect.NewClient[gen.GetPortfolioTransactionRequest, gen.PortfolioEvent]( - httpClient, - baseURL+PortfolioServiceGetPortfolioTransactionProcedure, - connect.WithSchema(portfolioServiceGetPortfolioTransactionMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - listPortfolioTransactions: connect.NewClient[gen.ListPortfolioTransactionsRequest, gen.ListPortfolioTransactionsResponse]( - httpClient, - baseURL+PortfolioServiceListPortfolioTransactionsProcedure, - connect.WithSchema(portfolioServiceListPortfolioTransactionsMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - updatePortfolioTransaction: connect.NewClient[gen.UpdatePortfolioTransactionRequest, gen.PortfolioEvent]( - httpClient, - baseURL+PortfolioServiceUpdatePortfolioTransactionProcedure, - connect.WithSchema(portfolioServiceUpdatePortfolioTransactionMethodDescriptor), - connect.WithClientOptions(opts...), - ), - deletePortfolioTransaction: connect.NewClient[gen.DeletePortfolioTransactionRequest, emptypb.Empty]( - httpClient, - baseURL+PortfolioServiceDeletePortfolioTransactionProcedure, - connect.WithSchema(portfolioServiceDeletePortfolioTransactionMethodDescriptor), - connect.WithClientOptions(opts...), - ), - importTransactions: connect.NewClient[gen.ImportTransactionsRequest, emptypb.Empty]( - httpClient, - baseURL+PortfolioServiceImportTransactionsProcedure, - connect.WithSchema(portfolioServiceImportTransactionsMethodDescriptor), - connect.WithClientOptions(opts...), - ), - createBankAccount: connect.NewClient[gen.CreateBankAccountRequest, gen.BankAccount]( - httpClient, - baseURL+PortfolioServiceCreateBankAccountProcedure, - connect.WithSchema(portfolioServiceCreateBankAccountMethodDescriptor), - connect.WithClientOptions(opts...), - ), - updateBankAccount: connect.NewClient[gen.UpdateBankAccountRequest, gen.BankAccount]( - httpClient, - baseURL+PortfolioServiceUpdateBankAccountProcedure, - connect.WithSchema(portfolioServiceUpdateBankAccountMethodDescriptor), - connect.WithClientOptions(opts...), - ), - deleteBankAccount: connect.NewClient[gen.DeleteBankAccountRequest, emptypb.Empty]( - httpClient, - baseURL+PortfolioServiceDeleteBankAccountProcedure, - connect.WithSchema(portfolioServiceDeleteBankAccountMethodDescriptor), - connect.WithClientOptions(opts...), - ), - } -} - -// portfolioServiceClient implements PortfolioServiceClient. -type portfolioServiceClient struct { - createPortfolio *connect.Client[gen.CreatePortfolioRequest, gen.Portfolio] - listPortfolios *connect.Client[gen.ListPortfoliosRequest, gen.ListPortfoliosResponse] - getPortfolio *connect.Client[gen.GetPortfolioRequest, gen.Portfolio] - updatePortfolio *connect.Client[gen.UpdatePortfolioRequest, gen.Portfolio] - deletePortfolio *connect.Client[gen.DeletePortfolioRequest, emptypb.Empty] - getPortfolioSnapshot *connect.Client[gen.GetPortfolioSnapshotRequest, gen.PortfolioSnapshot] - createPortfolioTransaction *connect.Client[gen.CreatePortfolioTransactionRequest, gen.PortfolioEvent] - getPortfolioTransaction *connect.Client[gen.GetPortfolioTransactionRequest, gen.PortfolioEvent] - listPortfolioTransactions *connect.Client[gen.ListPortfolioTransactionsRequest, gen.ListPortfolioTransactionsResponse] - updatePortfolioTransaction *connect.Client[gen.UpdatePortfolioTransactionRequest, gen.PortfolioEvent] - deletePortfolioTransaction *connect.Client[gen.DeletePortfolioTransactionRequest, emptypb.Empty] - importTransactions *connect.Client[gen.ImportTransactionsRequest, emptypb.Empty] - createBankAccount *connect.Client[gen.CreateBankAccountRequest, gen.BankAccount] - updateBankAccount *connect.Client[gen.UpdateBankAccountRequest, gen.BankAccount] - deleteBankAccount *connect.Client[gen.DeleteBankAccountRequest, emptypb.Empty] -} - -// CreatePortfolio calls mgo.portfolio.v1.PortfolioService.CreatePortfolio. -func (c *portfolioServiceClient) CreatePortfolio(ctx context.Context, req *connect.Request[gen.CreatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return c.createPortfolio.CallUnary(ctx, req) -} - -// ListPortfolios calls mgo.portfolio.v1.PortfolioService.ListPortfolios. -func (c *portfolioServiceClient) ListPortfolios(ctx context.Context, req *connect.Request[gen.ListPortfoliosRequest]) (*connect.Response[gen.ListPortfoliosResponse], error) { - return c.listPortfolios.CallUnary(ctx, req) -} - -// GetPortfolio calls mgo.portfolio.v1.PortfolioService.GetPortfolio. -func (c *portfolioServiceClient) GetPortfolio(ctx context.Context, req *connect.Request[gen.GetPortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return c.getPortfolio.CallUnary(ctx, req) -} - -// UpdatePortfolio calls mgo.portfolio.v1.PortfolioService.UpdatePortfolio. -func (c *portfolioServiceClient) UpdatePortfolio(ctx context.Context, req *connect.Request[gen.UpdatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return c.updatePortfolio.CallUnary(ctx, req) -} - -// DeletePortfolio calls mgo.portfolio.v1.PortfolioService.DeletePortfolio. -func (c *portfolioServiceClient) DeletePortfolio(ctx context.Context, req *connect.Request[gen.DeletePortfolioRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deletePortfolio.CallUnary(ctx, req) -} - -// GetPortfolioSnapshot calls mgo.portfolio.v1.PortfolioService.GetPortfolioSnapshot. -func (c *portfolioServiceClient) GetPortfolioSnapshot(ctx context.Context, req *connect.Request[gen.GetPortfolioSnapshotRequest]) (*connect.Response[gen.PortfolioSnapshot], error) { - return c.getPortfolioSnapshot.CallUnary(ctx, req) -} - -// CreatePortfolioTransaction calls mgo.portfolio.v1.PortfolioService.CreatePortfolioTransaction. -func (c *portfolioServiceClient) CreatePortfolioTransaction(ctx context.Context, req *connect.Request[gen.CreatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return c.createPortfolioTransaction.CallUnary(ctx, req) -} - -// GetPortfolioTransaction calls mgo.portfolio.v1.PortfolioService.GetPortfolioTransaction. -func (c *portfolioServiceClient) GetPortfolioTransaction(ctx context.Context, req *connect.Request[gen.GetPortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return c.getPortfolioTransaction.CallUnary(ctx, req) -} - -// ListPortfolioTransactions calls mgo.portfolio.v1.PortfolioService.ListPortfolioTransactions. -func (c *portfolioServiceClient) ListPortfolioTransactions(ctx context.Context, req *connect.Request[gen.ListPortfolioTransactionsRequest]) (*connect.Response[gen.ListPortfolioTransactionsResponse], error) { - return c.listPortfolioTransactions.CallUnary(ctx, req) -} - -// UpdatePortfolioTransaction calls mgo.portfolio.v1.PortfolioService.UpdatePortfolioTransaction. -func (c *portfolioServiceClient) UpdatePortfolioTransaction(ctx context.Context, req *connect.Request[gen.UpdatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return c.updatePortfolioTransaction.CallUnary(ctx, req) -} - -// DeletePortfolioTransaction calls mgo.portfolio.v1.PortfolioService.DeletePortfolioTransaction. -func (c *portfolioServiceClient) DeletePortfolioTransaction(ctx context.Context, req *connect.Request[gen.DeletePortfolioTransactionRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deletePortfolioTransaction.CallUnary(ctx, req) -} - -// ImportTransactions calls mgo.portfolio.v1.PortfolioService.ImportTransactions. -func (c *portfolioServiceClient) ImportTransactions(ctx context.Context, req *connect.Request[gen.ImportTransactionsRequest]) (*connect.Response[emptypb.Empty], error) { - return c.importTransactions.CallUnary(ctx, req) -} - -// CreateBankAccount calls mgo.portfolio.v1.PortfolioService.CreateBankAccount. -func (c *portfolioServiceClient) CreateBankAccount(ctx context.Context, req *connect.Request[gen.CreateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) { - return c.createBankAccount.CallUnary(ctx, req) -} - -// UpdateBankAccount calls mgo.portfolio.v1.PortfolioService.UpdateBankAccount. -func (c *portfolioServiceClient) UpdateBankAccount(ctx context.Context, req *connect.Request[gen.UpdateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) { - return c.updateBankAccount.CallUnary(ctx, req) -} - -// DeleteBankAccount calls mgo.portfolio.v1.PortfolioService.DeleteBankAccount. -func (c *portfolioServiceClient) DeleteBankAccount(ctx context.Context, req *connect.Request[gen.DeleteBankAccountRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deleteBankAccount.CallUnary(ctx, req) -} - -// PortfolioServiceHandler is an implementation of the mgo.portfolio.v1.PortfolioService service. -type PortfolioServiceHandler interface { - CreatePortfolio(context.Context, *connect.Request[gen.CreatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) - ListPortfolios(context.Context, *connect.Request[gen.ListPortfoliosRequest]) (*connect.Response[gen.ListPortfoliosResponse], error) - GetPortfolio(context.Context, *connect.Request[gen.GetPortfolioRequest]) (*connect.Response[gen.Portfolio], error) - UpdatePortfolio(context.Context, *connect.Request[gen.UpdatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) - DeletePortfolio(context.Context, *connect.Request[gen.DeletePortfolioRequest]) (*connect.Response[emptypb.Empty], error) - GetPortfolioSnapshot(context.Context, *connect.Request[gen.GetPortfolioSnapshotRequest]) (*connect.Response[gen.PortfolioSnapshot], error) - CreatePortfolioTransaction(context.Context, *connect.Request[gen.CreatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - GetPortfolioTransaction(context.Context, *connect.Request[gen.GetPortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - ListPortfolioTransactions(context.Context, *connect.Request[gen.ListPortfolioTransactionsRequest]) (*connect.Response[gen.ListPortfolioTransactionsResponse], error) - UpdatePortfolioTransaction(context.Context, *connect.Request[gen.UpdatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) - DeletePortfolioTransaction(context.Context, *connect.Request[gen.DeletePortfolioTransactionRequest]) (*connect.Response[emptypb.Empty], error) - ImportTransactions(context.Context, *connect.Request[gen.ImportTransactionsRequest]) (*connect.Response[emptypb.Empty], error) - CreateBankAccount(context.Context, *connect.Request[gen.CreateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) - UpdateBankAccount(context.Context, *connect.Request[gen.UpdateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) - DeleteBankAccount(context.Context, *connect.Request[gen.DeleteBankAccountRequest]) (*connect.Response[emptypb.Empty], error) -} - -// NewPortfolioServiceHandler builds an HTTP handler from the service implementation. It returns the -// path on which to mount the handler and the handler itself. -// -// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf -// and JSON codecs. They also support gzip compression. -func NewPortfolioServiceHandler(svc PortfolioServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { - portfolioServiceCreatePortfolioHandler := connect.NewUnaryHandler( - PortfolioServiceCreatePortfolioProcedure, - svc.CreatePortfolio, - connect.WithSchema(portfolioServiceCreatePortfolioMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceListPortfoliosHandler := connect.NewUnaryHandler( - PortfolioServiceListPortfoliosProcedure, - svc.ListPortfolios, - connect.WithSchema(portfolioServiceListPortfoliosMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceGetPortfolioHandler := connect.NewUnaryHandler( - PortfolioServiceGetPortfolioProcedure, - svc.GetPortfolio, - connect.WithSchema(portfolioServiceGetPortfolioMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceUpdatePortfolioHandler := connect.NewUnaryHandler( - PortfolioServiceUpdatePortfolioProcedure, - svc.UpdatePortfolio, - connect.WithSchema(portfolioServiceUpdatePortfolioMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceDeletePortfolioHandler := connect.NewUnaryHandler( - PortfolioServiceDeletePortfolioProcedure, - svc.DeletePortfolio, - connect.WithSchema(portfolioServiceDeletePortfolioMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceGetPortfolioSnapshotHandler := connect.NewUnaryHandler( - PortfolioServiceGetPortfolioSnapshotProcedure, - svc.GetPortfolioSnapshot, - connect.WithSchema(portfolioServiceGetPortfolioSnapshotMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceCreatePortfolioTransactionHandler := connect.NewUnaryHandler( - PortfolioServiceCreatePortfolioTransactionProcedure, - svc.CreatePortfolioTransaction, - connect.WithSchema(portfolioServiceCreatePortfolioTransactionMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceGetPortfolioTransactionHandler := connect.NewUnaryHandler( - PortfolioServiceGetPortfolioTransactionProcedure, - svc.GetPortfolioTransaction, - connect.WithSchema(portfolioServiceGetPortfolioTransactionMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceListPortfolioTransactionsHandler := connect.NewUnaryHandler( - PortfolioServiceListPortfolioTransactionsProcedure, - svc.ListPortfolioTransactions, - connect.WithSchema(portfolioServiceListPortfolioTransactionsMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceUpdatePortfolioTransactionHandler := connect.NewUnaryHandler( - PortfolioServiceUpdatePortfolioTransactionProcedure, - svc.UpdatePortfolioTransaction, - connect.WithSchema(portfolioServiceUpdatePortfolioTransactionMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceDeletePortfolioTransactionHandler := connect.NewUnaryHandler( - PortfolioServiceDeletePortfolioTransactionProcedure, - svc.DeletePortfolioTransaction, - connect.WithSchema(portfolioServiceDeletePortfolioTransactionMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceImportTransactionsHandler := connect.NewUnaryHandler( - PortfolioServiceImportTransactionsProcedure, - svc.ImportTransactions, - connect.WithSchema(portfolioServiceImportTransactionsMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceCreateBankAccountHandler := connect.NewUnaryHandler( - PortfolioServiceCreateBankAccountProcedure, - svc.CreateBankAccount, - connect.WithSchema(portfolioServiceCreateBankAccountMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceUpdateBankAccountHandler := connect.NewUnaryHandler( - PortfolioServiceUpdateBankAccountProcedure, - svc.UpdateBankAccount, - connect.WithSchema(portfolioServiceUpdateBankAccountMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - portfolioServiceDeleteBankAccountHandler := connect.NewUnaryHandler( - PortfolioServiceDeleteBankAccountProcedure, - svc.DeleteBankAccount, - connect.WithSchema(portfolioServiceDeleteBankAccountMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - return "/mgo.portfolio.v1.PortfolioService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case PortfolioServiceCreatePortfolioProcedure: - portfolioServiceCreatePortfolioHandler.ServeHTTP(w, r) - case PortfolioServiceListPortfoliosProcedure: - portfolioServiceListPortfoliosHandler.ServeHTTP(w, r) - case PortfolioServiceGetPortfolioProcedure: - portfolioServiceGetPortfolioHandler.ServeHTTP(w, r) - case PortfolioServiceUpdatePortfolioProcedure: - portfolioServiceUpdatePortfolioHandler.ServeHTTP(w, r) - case PortfolioServiceDeletePortfolioProcedure: - portfolioServiceDeletePortfolioHandler.ServeHTTP(w, r) - case PortfolioServiceGetPortfolioSnapshotProcedure: - portfolioServiceGetPortfolioSnapshotHandler.ServeHTTP(w, r) - case PortfolioServiceCreatePortfolioTransactionProcedure: - portfolioServiceCreatePortfolioTransactionHandler.ServeHTTP(w, r) - case PortfolioServiceGetPortfolioTransactionProcedure: - portfolioServiceGetPortfolioTransactionHandler.ServeHTTP(w, r) - case PortfolioServiceListPortfolioTransactionsProcedure: - portfolioServiceListPortfolioTransactionsHandler.ServeHTTP(w, r) - case PortfolioServiceUpdatePortfolioTransactionProcedure: - portfolioServiceUpdatePortfolioTransactionHandler.ServeHTTP(w, r) - case PortfolioServiceDeletePortfolioTransactionProcedure: - portfolioServiceDeletePortfolioTransactionHandler.ServeHTTP(w, r) - case PortfolioServiceImportTransactionsProcedure: - portfolioServiceImportTransactionsHandler.ServeHTTP(w, r) - case PortfolioServiceCreateBankAccountProcedure: - portfolioServiceCreateBankAccountHandler.ServeHTTP(w, r) - case PortfolioServiceUpdateBankAccountProcedure: - portfolioServiceUpdateBankAccountHandler.ServeHTTP(w, r) - case PortfolioServiceDeleteBankAccountProcedure: - portfolioServiceDeleteBankAccountHandler.ServeHTTP(w, r) - default: - http.NotFound(w, r) - } - }) -} - -// UnimplementedPortfolioServiceHandler returns CodeUnimplemented from all methods. -type UnimplementedPortfolioServiceHandler struct{} - -func (UnimplementedPortfolioServiceHandler) CreatePortfolio(context.Context, *connect.Request[gen.CreatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.CreatePortfolio is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) ListPortfolios(context.Context, *connect.Request[gen.ListPortfoliosRequest]) (*connect.Response[gen.ListPortfoliosResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.ListPortfolios is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) GetPortfolio(context.Context, *connect.Request[gen.GetPortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.GetPortfolio is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) UpdatePortfolio(context.Context, *connect.Request[gen.UpdatePortfolioRequest]) (*connect.Response[gen.Portfolio], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.UpdatePortfolio is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) DeletePortfolio(context.Context, *connect.Request[gen.DeletePortfolioRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.DeletePortfolio is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) GetPortfolioSnapshot(context.Context, *connect.Request[gen.GetPortfolioSnapshotRequest]) (*connect.Response[gen.PortfolioSnapshot], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.GetPortfolioSnapshot is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) CreatePortfolioTransaction(context.Context, *connect.Request[gen.CreatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.CreatePortfolioTransaction is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) GetPortfolioTransaction(context.Context, *connect.Request[gen.GetPortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.GetPortfolioTransaction is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) ListPortfolioTransactions(context.Context, *connect.Request[gen.ListPortfolioTransactionsRequest]) (*connect.Response[gen.ListPortfolioTransactionsResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.ListPortfolioTransactions is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) UpdatePortfolioTransaction(context.Context, *connect.Request[gen.UpdatePortfolioTransactionRequest]) (*connect.Response[gen.PortfolioEvent], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.UpdatePortfolioTransaction is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) DeletePortfolioTransaction(context.Context, *connect.Request[gen.DeletePortfolioTransactionRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.DeletePortfolioTransaction is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) ImportTransactions(context.Context, *connect.Request[gen.ImportTransactionsRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.ImportTransactions is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) CreateBankAccount(context.Context, *connect.Request[gen.CreateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.CreateBankAccount is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) UpdateBankAccount(context.Context, *connect.Request[gen.UpdateBankAccountRequest]) (*connect.Response[gen.BankAccount], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.UpdateBankAccount is not implemented")) -} - -func (UnimplementedPortfolioServiceHandler) DeleteBankAccount(context.Context, *connect.Request[gen.DeleteBankAccountRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.PortfolioService.DeleteBankAccount is not implemented")) -} - -// SecuritiesServiceClient is a client for the mgo.portfolio.v1.SecuritiesService service. -type SecuritiesServiceClient interface { - ListSecurities(context.Context, *connect.Request[gen.ListSecuritiesRequest]) (*connect.Response[gen.ListSecuritiesResponse], error) - GetSecurity(context.Context, *connect.Request[gen.GetSecurityRequest]) (*connect.Response[gen.Security], error) - CreateSecurity(context.Context, *connect.Request[gen.CreateSecurityRequest]) (*connect.Response[gen.Security], error) - UpdateSecurity(context.Context, *connect.Request[gen.UpdateSecurityRequest]) (*connect.Response[gen.Security], error) - DeleteSecurity(context.Context, *connect.Request[gen.DeleteSecurityRequest]) (*connect.Response[emptypb.Empty], error) - TriggerSecurityQuoteUpdate(context.Context, *connect.Request[gen.TriggerQuoteUpdateRequest]) (*connect.Response[gen.TriggerQuoteUpdateResponse], error) -} - -// NewSecuritiesServiceClient constructs a client for the mgo.portfolio.v1.SecuritiesService -// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for -// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply -// the connect.WithGRPC() or connect.WithGRPCWeb() options. -// -// The URL supplied here should be the base URL for the Connect or gRPC server (for example, -// http://api.acme.com or https://acme.com/grpc). -func NewSecuritiesServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SecuritiesServiceClient { - baseURL = strings.TrimRight(baseURL, "/") - return &securitiesServiceClient{ - listSecurities: connect.NewClient[gen.ListSecuritiesRequest, gen.ListSecuritiesResponse]( - httpClient, - baseURL+SecuritiesServiceListSecuritiesProcedure, - connect.WithSchema(securitiesServiceListSecuritiesMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - getSecurity: connect.NewClient[gen.GetSecurityRequest, gen.Security]( - httpClient, - baseURL+SecuritiesServiceGetSecurityProcedure, - connect.WithSchema(securitiesServiceGetSecurityMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithClientOptions(opts...), - ), - createSecurity: connect.NewClient[gen.CreateSecurityRequest, gen.Security]( - httpClient, - baseURL+SecuritiesServiceCreateSecurityProcedure, - connect.WithSchema(securitiesServiceCreateSecurityMethodDescriptor), - connect.WithClientOptions(opts...), - ), - updateSecurity: connect.NewClient[gen.UpdateSecurityRequest, gen.Security]( - httpClient, - baseURL+SecuritiesServiceUpdateSecurityProcedure, - connect.WithSchema(securitiesServiceUpdateSecurityMethodDescriptor), - connect.WithClientOptions(opts...), - ), - deleteSecurity: connect.NewClient[gen.DeleteSecurityRequest, emptypb.Empty]( - httpClient, - baseURL+SecuritiesServiceDeleteSecurityProcedure, - connect.WithSchema(securitiesServiceDeleteSecurityMethodDescriptor), - connect.WithClientOptions(opts...), - ), - triggerSecurityQuoteUpdate: connect.NewClient[gen.TriggerQuoteUpdateRequest, gen.TriggerQuoteUpdateResponse]( - httpClient, - baseURL+SecuritiesServiceTriggerSecurityQuoteUpdateProcedure, - connect.WithSchema(securitiesServiceTriggerSecurityQuoteUpdateMethodDescriptor), - connect.WithClientOptions(opts...), - ), - } -} - -// securitiesServiceClient implements SecuritiesServiceClient. -type securitiesServiceClient struct { - listSecurities *connect.Client[gen.ListSecuritiesRequest, gen.ListSecuritiesResponse] - getSecurity *connect.Client[gen.GetSecurityRequest, gen.Security] - createSecurity *connect.Client[gen.CreateSecurityRequest, gen.Security] - updateSecurity *connect.Client[gen.UpdateSecurityRequest, gen.Security] - deleteSecurity *connect.Client[gen.DeleteSecurityRequest, emptypb.Empty] - triggerSecurityQuoteUpdate *connect.Client[gen.TriggerQuoteUpdateRequest, gen.TriggerQuoteUpdateResponse] -} - -// ListSecurities calls mgo.portfolio.v1.SecuritiesService.ListSecurities. -func (c *securitiesServiceClient) ListSecurities(ctx context.Context, req *connect.Request[gen.ListSecuritiesRequest]) (*connect.Response[gen.ListSecuritiesResponse], error) { - return c.listSecurities.CallUnary(ctx, req) -} - -// GetSecurity calls mgo.portfolio.v1.SecuritiesService.GetSecurity. -func (c *securitiesServiceClient) GetSecurity(ctx context.Context, req *connect.Request[gen.GetSecurityRequest]) (*connect.Response[gen.Security], error) { - return c.getSecurity.CallUnary(ctx, req) -} - -// CreateSecurity calls mgo.portfolio.v1.SecuritiesService.CreateSecurity. -func (c *securitiesServiceClient) CreateSecurity(ctx context.Context, req *connect.Request[gen.CreateSecurityRequest]) (*connect.Response[gen.Security], error) { - return c.createSecurity.CallUnary(ctx, req) -} - -// UpdateSecurity calls mgo.portfolio.v1.SecuritiesService.UpdateSecurity. -func (c *securitiesServiceClient) UpdateSecurity(ctx context.Context, req *connect.Request[gen.UpdateSecurityRequest]) (*connect.Response[gen.Security], error) { - return c.updateSecurity.CallUnary(ctx, req) -} - -// DeleteSecurity calls mgo.portfolio.v1.SecuritiesService.DeleteSecurity. -func (c *securitiesServiceClient) DeleteSecurity(ctx context.Context, req *connect.Request[gen.DeleteSecurityRequest]) (*connect.Response[emptypb.Empty], error) { - return c.deleteSecurity.CallUnary(ctx, req) -} - -// TriggerSecurityQuoteUpdate calls mgo.portfolio.v1.SecuritiesService.TriggerSecurityQuoteUpdate. -func (c *securitiesServiceClient) TriggerSecurityQuoteUpdate(ctx context.Context, req *connect.Request[gen.TriggerQuoteUpdateRequest]) (*connect.Response[gen.TriggerQuoteUpdateResponse], error) { - return c.triggerSecurityQuoteUpdate.CallUnary(ctx, req) -} - -// SecuritiesServiceHandler is an implementation of the mgo.portfolio.v1.SecuritiesService service. -type SecuritiesServiceHandler interface { - ListSecurities(context.Context, *connect.Request[gen.ListSecuritiesRequest]) (*connect.Response[gen.ListSecuritiesResponse], error) - GetSecurity(context.Context, *connect.Request[gen.GetSecurityRequest]) (*connect.Response[gen.Security], error) - CreateSecurity(context.Context, *connect.Request[gen.CreateSecurityRequest]) (*connect.Response[gen.Security], error) - UpdateSecurity(context.Context, *connect.Request[gen.UpdateSecurityRequest]) (*connect.Response[gen.Security], error) - DeleteSecurity(context.Context, *connect.Request[gen.DeleteSecurityRequest]) (*connect.Response[emptypb.Empty], error) - TriggerSecurityQuoteUpdate(context.Context, *connect.Request[gen.TriggerQuoteUpdateRequest]) (*connect.Response[gen.TriggerQuoteUpdateResponse], error) -} - -// NewSecuritiesServiceHandler builds an HTTP handler from the service implementation. It returns -// the path on which to mount the handler and the handler itself. -// -// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf -// and JSON codecs. They also support gzip compression. -func NewSecuritiesServiceHandler(svc SecuritiesServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { - securitiesServiceListSecuritiesHandler := connect.NewUnaryHandler( - SecuritiesServiceListSecuritiesProcedure, - svc.ListSecurities, - connect.WithSchema(securitiesServiceListSecuritiesMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - securitiesServiceGetSecurityHandler := connect.NewUnaryHandler( - SecuritiesServiceGetSecurityProcedure, - svc.GetSecurity, - connect.WithSchema(securitiesServiceGetSecurityMethodDescriptor), - connect.WithIdempotency(connect.IdempotencyNoSideEffects), - connect.WithHandlerOptions(opts...), - ) - securitiesServiceCreateSecurityHandler := connect.NewUnaryHandler( - SecuritiesServiceCreateSecurityProcedure, - svc.CreateSecurity, - connect.WithSchema(securitiesServiceCreateSecurityMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - securitiesServiceUpdateSecurityHandler := connect.NewUnaryHandler( - SecuritiesServiceUpdateSecurityProcedure, - svc.UpdateSecurity, - connect.WithSchema(securitiesServiceUpdateSecurityMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - securitiesServiceDeleteSecurityHandler := connect.NewUnaryHandler( - SecuritiesServiceDeleteSecurityProcedure, - svc.DeleteSecurity, - connect.WithSchema(securitiesServiceDeleteSecurityMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - securitiesServiceTriggerSecurityQuoteUpdateHandler := connect.NewUnaryHandler( - SecuritiesServiceTriggerSecurityQuoteUpdateProcedure, - svc.TriggerSecurityQuoteUpdate, - connect.WithSchema(securitiesServiceTriggerSecurityQuoteUpdateMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - return "/mgo.portfolio.v1.SecuritiesService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case SecuritiesServiceListSecuritiesProcedure: - securitiesServiceListSecuritiesHandler.ServeHTTP(w, r) - case SecuritiesServiceGetSecurityProcedure: - securitiesServiceGetSecurityHandler.ServeHTTP(w, r) - case SecuritiesServiceCreateSecurityProcedure: - securitiesServiceCreateSecurityHandler.ServeHTTP(w, r) - case SecuritiesServiceUpdateSecurityProcedure: - securitiesServiceUpdateSecurityHandler.ServeHTTP(w, r) - case SecuritiesServiceDeleteSecurityProcedure: - securitiesServiceDeleteSecurityHandler.ServeHTTP(w, r) - case SecuritiesServiceTriggerSecurityQuoteUpdateProcedure: - securitiesServiceTriggerSecurityQuoteUpdateHandler.ServeHTTP(w, r) - default: - http.NotFound(w, r) - } - }) -} - -// UnimplementedSecuritiesServiceHandler returns CodeUnimplemented from all methods. -type UnimplementedSecuritiesServiceHandler struct{} - -func (UnimplementedSecuritiesServiceHandler) ListSecurities(context.Context, *connect.Request[gen.ListSecuritiesRequest]) (*connect.Response[gen.ListSecuritiesResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.ListSecurities is not implemented")) -} - -func (UnimplementedSecuritiesServiceHandler) GetSecurity(context.Context, *connect.Request[gen.GetSecurityRequest]) (*connect.Response[gen.Security], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.GetSecurity is not implemented")) -} - -func (UnimplementedSecuritiesServiceHandler) CreateSecurity(context.Context, *connect.Request[gen.CreateSecurityRequest]) (*connect.Response[gen.Security], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.CreateSecurity is not implemented")) -} - -func (UnimplementedSecuritiesServiceHandler) UpdateSecurity(context.Context, *connect.Request[gen.UpdateSecurityRequest]) (*connect.Response[gen.Security], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.UpdateSecurity is not implemented")) -} - -func (UnimplementedSecuritiesServiceHandler) DeleteSecurity(context.Context, *connect.Request[gen.DeleteSecurityRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.DeleteSecurity is not implemented")) -} - -func (UnimplementedSecuritiesServiceHandler) TriggerSecurityQuoteUpdate(context.Context, *connect.Request[gen.TriggerQuoteUpdateRequest]) (*connect.Response[gen.TriggerQuoteUpdateResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgo.portfolio.v1.SecuritiesService.TriggerSecurityQuoteUpdate is not implemented")) -} diff --git a/gen/securities_sql.go b/gen/securities_sql.go deleted file mode 100644 index 49359fa0..00000000 --- a/gen/securities_sql.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfoliov1 - -import ( - "database/sql" - "strings" - "time" - - "github.com/oxisto/money-gopher/persistence" - - "google.golang.org/protobuf/types/known/timestamppb" -) - -var _ persistence.StorageObject = &Security{} - -func (*Security) InitTables(db *persistence.DB) (err error) { - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS securities ( -id TEXT PRIMARY KEY, -display_name TEXT NOT NULL, -quote_provider TEXT -);`) - if err != nil { - return err - } - - return -} - -func (*ListedSecurity) InitTables(db *persistence.DB) (err error) { - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS listed_securities ( -security_id TEXT, -ticker TEXT NOT NULL, -currency TEXT NOT NULL, -latest_quote INTEGER, -latest_quote_timestamp DATETIME, -PRIMARY KEY (security_id, ticker) -);`) - if err != nil { - return err - } - - return -} - -func (*Security) PrepareReplace(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`REPLACE INTO securities (id, display_name, quote_provider) VALUES (?,?,?);`) -} - -func (*ListedSecurity) PrepareReplace(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`REPLACE INTO listed_securities (security_id, ticker, currency, latest_quote, latest_quote_timestamp) VALUES (?,?,?,?,?);`) -} - -func (*Security) PrepareList(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name, quote_provider FROM securities`) -} - -func (*ListedSecurity) PrepareList(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT security_id, ticker, currency, latest_quote, latest_quote_timestamp FROM listed_securities WHERE security_id = ?`) -} - -func (*Security) PrepareGet(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT id, display_name, quote_provider FROM securities WHERE id = ?`) -} - -func (*ListedSecurity) PrepareGet(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`SELECT * FROM listed_securities WHERE security_id = ? AND ticker = ?`) -} - -func (*Security) PrepareUpdate(db *persistence.DB, columns []string) (stmt *sql.Stmt, err error) { - // We need to make sure to quote columns here because they are potentially evil user input - var ( - query string - set []string - ) - - set = make([]string, len(columns)) - for i, col := range columns { - set[i] = persistence.Quote(col) + " = ?" - } - - query += "UPDATE securities SET " + strings.Join(set, ", ") + " WHERE id = ?;" - - return db.Prepare(query) -} - -func (*ListedSecurity) PrepareUpdate(db *persistence.DB, columns []string) (stmt *sql.Stmt, err error) { - // We need to make sure to quote columns here because they are potentially evil user input - var ( - query string - set []string - ) - - set = make([]string, len(columns)) - for i, col := range columns { - set[i] = persistence.Quote(col) + " = ?" - } - - query += "UPDATE listed_securities SET " + strings.Join(set, ", ") + " WHERE security_id = ? AND ticker = ?;" - - return db.Prepare(query) -} - -func (*Security) PrepareDelete(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`DELETE FROM securities WHERE id = ?`) -} - -func (*ListedSecurity) PrepareDelete(db *persistence.DB) (stmt *sql.Stmt, err error) { - return db.Prepare(`DELETE FROM listed_securities WHERE security_id = ? AND ticker = ?`) -} - -func (s *Security) ReplaceIntoArgs() []any { - return []any{s.Id, s.DisplayName, s.QuoteProvider} -} - -func (l *ListedSecurity) ReplaceIntoArgs() []any { - var ( - pt *time.Time - t time.Time - value sql.NullInt32 - ) - - if l.LatestQuoteTimestamp != nil { - t = l.LatestQuoteTimestamp.AsTime() - pt = &t - } - - if l.LatestQuote != nil { - value.Int32 = l.LatestQuote.Value - value.Valid = true - } - - return []any{l.SecurityId, l.Ticker, l.Currency, value, pt} -} - -func (s *Security) UpdateArgs(columns []string) (args []any) { - for _, col := range columns { - switch col { - case "id": - args = append(args, s.Id) - case "display_name": - args = append(args, s.DisplayName) - case "quote_provider": - args = append(args, s.QuoteProvider) - } - } - - return args -} - -func (l *ListedSecurity) UpdateArgs(columns []string) (args []any) { - for _, col := range columns { - switch col { - case "security_id": - args = append(args, l.SecurityId) - case "ticker": - args = append(args, l.Ticker) - case "currency": - args = append(args, l.LatestQuote.GetSymbol()) - case "latest_quote": - args = append(args, l.LatestQuote.GetValue()) - case "latest_quote_timestamp": - if l.LatestQuoteTimestamp != nil { - args = append(args, l.LatestQuoteTimestamp.AsTime()) - } else { - args = append(args, nil) - } - } - } - - return args -} - -func (*Security) Scan(sc persistence.Scanner) (obj persistence.StorageObject, err error) { - var ( - s Security - ) - - err = sc.Scan(&s.Id, &s.DisplayName, &s.QuoteProvider) - if err != nil { - return nil, err - } - - return &s, nil -} - -func (*ListedSecurity) Scan(sc persistence.Scanner) (obj persistence.StorageObject, err error) { - var ( - l ListedSecurity - t sql.NullTime - value sql.NullInt32 - ) - - err = sc.Scan(&l.SecurityId, &l.Ticker, &l.Currency, &value, &t) - if err != nil { - return nil, err - } - - if t.Valid { - l.LatestQuoteTimestamp = timestamppb.New(t.Time) - } - - if value.Valid { - l.LatestQuote = Value(value.Int32) - l.LatestQuote.Symbol = l.Currency - } - - return &l, nil -} diff --git a/generate.go b/generate.go index c45400e9..cb9d311c 100644 --- a/generate.go +++ b/generate.go @@ -15,8 +15,6 @@ // This file is part of The Money Gopher. //go:generate sqlc generate -//go:generate buf generate -//go:generate buf format -w -//go:generate buf generate --template buf.openapi.gen.yaml --path mgo.proto -o . +//go:generate gqlgen package moneygopher diff --git a/go.mod b/go.mod index 3050a9fa..20a39f69 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,11 @@ module github.com/oxisto/money-gopher -go 1.22.1 +go 1.23.4 require ( - connectrpc.com/connect v1.16.2 - connectrpc.com/vanguard v0.3.0 - github.com/MicahParks/keyfunc/v3 v3.3.5 + github.com/99designs/gqlgen v0.17.61 github.com/fatih/color v1.18.0 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 github.com/lmittmann/tint v1.0.6 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 @@ -15,24 +13,26 @@ require ( github.com/oxisto/assert v0.1.2 github.com/oxisto/oauth2go v0.14.0 github.com/pressly/goose/v3 v3.24.0 + github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 github.com/urfave/cli/v3 v3.0.0-beta1 + github.com/vektah/gqlparser/v2 v2.5.20 golang.org/x/net v0.33.0 - golang.org/x/text v0.21.0 - google.golang.org/protobuf v1.36.1 + golang.org/x/sync v0.10.0 ) -require github.com/google/go-cmp v0.6.0 // indirect - require ( - github.com/MicahParks/jwkset v0.5.19 // indirect + github.com/agnivade/levenshtein v1.2.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sync v0.10.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sys v0.28.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def - google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect + golang.org/x/text v0.21.0 // indirect ) + +replace github.com/99designs/gqlgen v0.17.61 => github.com/oxisto/gqlgen v0.17.62-0.20241227140449-4bf1c5c27bad diff --git a/go.sum b/go.sum index 6d46dbb0..9625f095 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,25 @@ -connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= -connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= -connectrpc.com/vanguard v0.3.0 h1:prUKFm8rYDwvpvnOSoqdUowPMK0tRA0pbSrQoMd6Zng= -connectrpc.com/vanguard v0.3.0/go.mod h1:nxQ7+N6qhBiQczqGwdTw4oCqx1rDryIt20cEdECqToM= -github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw= -github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= -github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo= -github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8= +github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0= +github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -35,12 +39,10 @@ github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6B github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/oxisto/assert v0.0.6 h1:Z/wRt0qndURRof+eOGr7GbcJ6BHZT2nyZd9diuZHS8o= -github.com/oxisto/assert v0.0.6/go.mod h1:07ANKfyBm6j+pZk1qArFueno6fCoEGKvPbPeJSQkH3s= -github.com/oxisto/assert v0.1.1 h1:y9A0ymf7i3RdAYF4CBYc+jRU+wBaVmsp1RJ4Yd73csw= -github.com/oxisto/assert v0.1.1/go.mod h1:3vg52jeU6iN+pplw4n2C+zHNit9+04Wr9qqty4EU9Mc= github.com/oxisto/assert v0.1.2 h1:atb9lmltuakIcA/K7QvXbXKBSWsXKaVFFBZL8u1icHk= github.com/oxisto/assert v0.1.2/go.mod h1:3vg52jeU6iN+pplw4n2C+zHNit9+04Wr9qqty4EU9Mc= +github.com/oxisto/gqlgen v0.17.62-0.20241227140449-4bf1c5c27bad h1:DHqTk1/Am9hh5JBfLSZir5ve8NNQe0/LrwlEAf3vx/E= +github.com/oxisto/gqlgen v0.17.62-0.20241227140449-4bf1c5c27bad/go.mod h1:vBeGFxYsZAp+OZ7DkbyiMwai2BnRsdIvIDhd6YN7olM= github.com/oxisto/oauth2go v0.14.0 h1:VjMJCBC3TxnXPEANWWsZudKlGYh06YgBl49fb5JhavM= github.com/oxisto/oauth2go v0.14.0/go.mod h1:8mUk9Gsrh4xgzrVLsliSGi/X3+ZQkXztfK+dpdAkRZM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -49,20 +51,28 @@ github.com/pressly/goose/v3 v3.24.0 h1:sFbNms7Bd++2VMq6HSgDHDLWa7kHz1qXzPb3ZIU72 github.com/pressly/goose/v3 v3.24.0/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +github.com/vektah/gqlparser/v2 v2.5.20 h1:kPaWbhBntxoZPaNdBaIPT1Kh0i1b/onb5kXgEdP5JCo= +github.com/vektah/gqlparser/v2 v2.5.20/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -71,22 +81,6 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0= -google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4= -google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def h1:0Km0hi+g2KXbXL0+riZzSCKz23f4MmwicuEb00JeonI= -google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def/go.mod h1:u2DoMSpCXjrzqLdobRccQMc9wrnMAJ1DLng0a2yqM2Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= diff --git a/gqlgen.yml b/gqlgen.yml new file mode 100644 index 00000000..03407afb --- /dev/null +++ b/gqlgen.yml @@ -0,0 +1,124 @@ +# Where are all the schema files located? globs are supported eg src/**/*.graphqls +schema: + - graph/*.graphqls + +# Where should the generated server code go? +exec: + package: graph + layout: single-file # Only other option is "follow-schema," ie multi-file. + + # Only for single-file layout: + filename: graph/generated.go + + # Only for follow-schema layout: + # dir: graph + # filename_template: "{name}.generated.go" + + # Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited) + # worker_limit: 1000 + +# Uncomment to enable federation +# federation: +# filename: graph/federation.go +# package: graph +# version: 2 +# options: +# computed_requires: true + +# Where should any generated models go? +model: + filename: models/models_gen.go + package: models + + # Optional: Pass in a path to a new gotpl template to use for generating the models + # model_template: [your/path/model.gotpl] + +# Where should the resolver implementations go? +resolver: + package: graph + layout: follow-schema # Only other option is "single-file." + + # Only for single-file layout: + # filename: graph/resolver.go + + # Only for follow-schema layout: + dir: graph + filename_template: "{name}.resolvers.go" + + # Optional: turn on to not generate template comments above resolvers + omit_template_comment: false + + # Optional: Pass in a path to a new gotpl template to use for generating resolvers + # resolver_template: [your/path/resolver.gotpl] + # Optional: turn on to avoid rewriting existing resolver(s) when generating + # preserve_resolver: false + + # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models + # struct_tag: json + +# Optional: turn on to use []Thing instead of []*Thing +# omit_slice_element_pointers: true + +# Optional: turn on to omit Is() methods to interface and unions +# omit_interface_checks: true + +# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function +# omit_complexity: false + +# Optional: turn on to not generate any file notice comments in generated files +# omit_gqlgen_file_notice: false + +# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true. +# omit_gqlgen_version_in_file_notice: false + +# Optional: turn on to exclude root models such as Query and Mutation from the generated models file. +# omit_root_models: false + +# Optional: turn on to exclude resolver fields from the generated models file. +# omit_resolver_fields: false + +# Optional: turn off to make struct-type struct fields not use pointers +# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } +# struct_fields_always_pointers: true + +# Optional: turn off to make resolvers return values instead of pointers for structs +# resolvers_always_return_pointers: true + +# Optional: turn on to return pointers instead of values in unmarshalInput +# return_pointers_in_unmarshalinput: false + +# Optional: wrap nullable input fields with Omittable +# nullable_input_omittable: true + +# Optional: set to speed up generation time by not performing a final validation pass. +# skip_validation: true + +# Optional: set to skip running `go mod tidy` when generating server code +# skip_mod_tidy: true + +# Optional: if this is set to true, argument directives that +# decorate a field with a null value will still be called. +# +# This enables argumment directives to not just mutate +# argument values but to set them even if they're null. +call_argument_directives_with_null: true + +# Optional: set build tags that will be used to load packages +# go_build_tags: +# - private +# - enterprise + +# Optional: set to modify the initialisms regarded for Go names +# go_initialisms: +# replace_defaults: false # if true, the default initialisms will get dropped in favor of the new ones instead of being added +# initialisms: # List of initialisms to for Go names +# - 'CC' +# - 'BCC' + +# gqlgen will search for any type names in the schema in these go packages +# if they match it will use them, otherwise it will generate them. +autobind: + - "github.com/oxisto/money-gopher/persistence" + - "github.com/oxisto/money-gopher/portfolio/events" + - "github.com/oxisto/money-gopher/portfolio/accounts" + - "github.com/oxisto/money-gopher/currency" diff --git a/graph/generated.go b/graph/generated.go new file mode 100644 index 00000000..80942d1e --- /dev/null +++ b/graph/generated.go @@ -0,0 +1,9695 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package graph + +import ( + "bytes" + "context" + "embed" + "errors" + "fmt" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/introspection" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/models" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" + gqlparser "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" +) + +// region ************************** generated!.gotpl ************************** + +// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface. +func NewExecutableSchema(cfg Config) graphql.ExecutableSchema { + return &executableSchema{ + schema: cfg.Schema, + resolvers: cfg.Resolvers, + directives: cfg.Directives, + complexity: cfg.Complexity, + } +} + +type Config struct { + Schema *ast.Schema + Resolvers ResolverRoot + Directives DirectiveRoot + Complexity ComplexityRoot +} + +type ResolverRoot interface { + Account() AccountResolver + ListedSecurity() ListedSecurityResolver + Mutation() MutationResolver + Portfolio() PortfolioResolver + Query() QueryResolver + Security() SecurityResolver + Transaction() TransactionResolver +} + +type DirectiveRoot struct { +} + +type ComplexityRoot struct { + Account struct { + DisplayName func(childComplexity int) int + ID func(childComplexity int) int + ReferenceAccount func(childComplexity int) int + Type func(childComplexity int) int + } + + Currency struct { + Amount func(childComplexity int) int + Symbol func(childComplexity int) int + } + + ListedSecurity struct { + Currency func(childComplexity int) int + LatestQuote func(childComplexity int) int + LatestQuoteTimestamp func(childComplexity int) int + Security func(childComplexity int) int + Ticker func(childComplexity int) int + } + + Mutation struct { + CreateAccount func(childComplexity int, input models.AccountInput) int + CreatePortfolio func(childComplexity int, input models.PortfolioInput) int + CreateSecurity func(childComplexity int, input models.SecurityInput) int + CreateTransaction func(childComplexity int, input models.TransactionInput) int + DeleteAccount func(childComplexity int, id string) int + TriggerQuoteUpdate func(childComplexity int, securityIDs []string) int + UpdatePortfolio func(childComplexity int, id string, input models.PortfolioInput) int + UpdateSecurity func(childComplexity int, id string, input models.SecurityInput) int + UpdateTransaction func(childComplexity int, id string, input models.TransactionInput) int + } + + Portfolio struct { + Accounts func(childComplexity int) int + DisplayName func(childComplexity int) int + Events func(childComplexity int) int + ID func(childComplexity int) int + Snapshot func(childComplexity int, when *time.Time) int + } + + PortfolioEvent struct { + Security func(childComplexity int) int + Time func(childComplexity int) int + Type func(childComplexity int) int + } + + PortfolioPosition struct { + Amount func(childComplexity int) int + Gains func(childComplexity int) int + MarketPrice func(childComplexity int) int + MarketValue func(childComplexity int) int + ProfitOrLoss func(childComplexity int) int + PurchasePrice func(childComplexity int) int + PurchaseValue func(childComplexity int) int + Security func(childComplexity int) int + TotalFees func(childComplexity int) int + } + + PortfolioSnapshot struct { + Cash func(childComplexity int) int + FirstTransactionTime func(childComplexity int) int + Positions func(childComplexity int) int + Time func(childComplexity int) int + TotalGains func(childComplexity int) int + TotalMarketValue func(childComplexity int) int + TotalPortfolioValue func(childComplexity int) int + TotalProfitOrLoss func(childComplexity int) int + TotalPurchaseValue func(childComplexity int) int + } + + Query struct { + Account func(childComplexity int, id string) int + Accounts func(childComplexity int) int + Portfolio func(childComplexity int, id string) int + Portfolios func(childComplexity int) int + Securities func(childComplexity int) int + Security func(childComplexity int, id string) int + Transactions func(childComplexity int, accountID string) int + } + + Security struct { + DisplayName func(childComplexity int) int + ID func(childComplexity int) int + ListedAs func(childComplexity int) int + QuoteProvider func(childComplexity int) int + } + + Transaction struct { + Amount func(childComplexity int) int + DestinationAccount func(childComplexity int) int + Fees func(childComplexity int) int + ID func(childComplexity int) int + Price func(childComplexity int) int + Security func(childComplexity int) int + SourceAccount func(childComplexity int) int + Time func(childComplexity int) int + Type func(childComplexity int) int + } +} + +type AccountResolver interface { + ReferenceAccount(ctx context.Context, obj *persistence.Account) (*persistence.Account, error) +} +type ListedSecurityResolver interface { + Security(ctx context.Context, obj *persistence.ListedSecurity) (*persistence.Security, error) +} +type MutationResolver interface { + CreateSecurity(ctx context.Context, input models.SecurityInput) (*persistence.Security, error) + UpdateSecurity(ctx context.Context, id string, input models.SecurityInput) (*persistence.Security, error) + CreatePortfolio(ctx context.Context, input models.PortfolioInput) (*persistence.Portfolio, error) + UpdatePortfolio(ctx context.Context, id string, input models.PortfolioInput) (*persistence.Portfolio, error) + CreateAccount(ctx context.Context, input models.AccountInput) (*persistence.Account, error) + DeleteAccount(ctx context.Context, id string) (*persistence.Account, error) + CreateTransaction(ctx context.Context, input models.TransactionInput) (*persistence.Transaction, error) + UpdateTransaction(ctx context.Context, id string, input models.TransactionInput) (*persistence.Transaction, error) + TriggerQuoteUpdate(ctx context.Context, securityIDs []string) ([]*persistence.ListedSecurity, error) +} +type PortfolioResolver interface { + Accounts(ctx context.Context, obj *persistence.Portfolio) ([]*persistence.Account, error) + Snapshot(ctx context.Context, obj *persistence.Portfolio, when *time.Time) (*models.PortfolioSnapshot, error) + Events(ctx context.Context, obj *persistence.Portfolio) ([]*models.PortfolioEvent, error) +} +type QueryResolver interface { + Security(ctx context.Context, id string) (*persistence.Security, error) + Securities(ctx context.Context) ([]*persistence.Security, error) + Portfolio(ctx context.Context, id string) (*persistence.Portfolio, error) + Portfolios(ctx context.Context) ([]*persistence.Portfolio, error) + Account(ctx context.Context, id string) (*persistence.Account, error) + Accounts(ctx context.Context) ([]*persistence.Account, error) + Transactions(ctx context.Context, accountID string) ([]*persistence.Transaction, error) +} +type SecurityResolver interface { + ListedAs(ctx context.Context, obj *persistence.Security) ([]*persistence.ListedSecurity, error) +} +type TransactionResolver interface { + SourceAccount(ctx context.Context, obj *persistence.Transaction) (*persistence.Account, error) + DestinationAccount(ctx context.Context, obj *persistence.Transaction) (*persistence.Account, error) + Security(ctx context.Context, obj *persistence.Transaction) (*persistence.Security, error) +} + +type executableSchema struct { + schema *ast.Schema + resolvers ResolverRoot + directives DirectiveRoot + complexity ComplexityRoot +} + +func (e *executableSchema) Schema() *ast.Schema { + if e.schema != nil { + return e.schema + } + return parsedSchema +} + +func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]any) (int, bool) { + ec := executionContext{nil, e, 0, 0, nil} + _ = ec + switch typeName + "." + field { + + case "Account.displayName": + if e.complexity.Account.DisplayName == nil { + break + } + + return e.complexity.Account.DisplayName(childComplexity), true + + case "Account.id": + if e.complexity.Account.ID == nil { + break + } + + return e.complexity.Account.ID(childComplexity), true + + case "Account.referenceAccount": + if e.complexity.Account.ReferenceAccount == nil { + break + } + + return e.complexity.Account.ReferenceAccount(childComplexity), true + + case "Account.type": + if e.complexity.Account.Type == nil { + break + } + + return e.complexity.Account.Type(childComplexity), true + + case "Currency.amount": + if e.complexity.Currency.Amount == nil { + break + } + + return e.complexity.Currency.Amount(childComplexity), true + + case "Currency.symbol": + if e.complexity.Currency.Symbol == nil { + break + } + + return e.complexity.Currency.Symbol(childComplexity), true + + case "ListedSecurity.currency": + if e.complexity.ListedSecurity.Currency == nil { + break + } + + return e.complexity.ListedSecurity.Currency(childComplexity), true + + case "ListedSecurity.latestQuote": + if e.complexity.ListedSecurity.LatestQuote == nil { + break + } + + return e.complexity.ListedSecurity.LatestQuote(childComplexity), true + + case "ListedSecurity.latestQuoteTimestamp": + if e.complexity.ListedSecurity.LatestQuoteTimestamp == nil { + break + } + + return e.complexity.ListedSecurity.LatestQuoteTimestamp(childComplexity), true + + case "ListedSecurity.security": + if e.complexity.ListedSecurity.Security == nil { + break + } + + return e.complexity.ListedSecurity.Security(childComplexity), true + + case "ListedSecurity.ticker": + if e.complexity.ListedSecurity.Ticker == nil { + break + } + + return e.complexity.ListedSecurity.Ticker(childComplexity), true + + case "Mutation.createAccount": + if e.complexity.Mutation.CreateAccount == nil { + break + } + + args, err := ec.field_Mutation_createAccount_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateAccount(childComplexity, args["input"].(models.AccountInput)), true + + case "Mutation.createPortfolio": + if e.complexity.Mutation.CreatePortfolio == nil { + break + } + + args, err := ec.field_Mutation_createPortfolio_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreatePortfolio(childComplexity, args["input"].(models.PortfolioInput)), true + + case "Mutation.createSecurity": + if e.complexity.Mutation.CreateSecurity == nil { + break + } + + args, err := ec.field_Mutation_createSecurity_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateSecurity(childComplexity, args["input"].(models.SecurityInput)), true + + case "Mutation.createTransaction": + if e.complexity.Mutation.CreateTransaction == nil { + break + } + + args, err := ec.field_Mutation_createTransaction_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateTransaction(childComplexity, args["input"].(models.TransactionInput)), true + + case "Mutation.deleteAccount": + if e.complexity.Mutation.DeleteAccount == nil { + break + } + + args, err := ec.field_Mutation_deleteAccount_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteAccount(childComplexity, args["id"].(string)), true + + case "Mutation.triggerQuoteUpdate": + if e.complexity.Mutation.TriggerQuoteUpdate == nil { + break + } + + args, err := ec.field_Mutation_triggerQuoteUpdate_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.TriggerQuoteUpdate(childComplexity, args["securityIDs"].([]string)), true + + case "Mutation.updatePortfolio": + if e.complexity.Mutation.UpdatePortfolio == nil { + break + } + + args, err := ec.field_Mutation_updatePortfolio_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdatePortfolio(childComplexity, args["id"].(string), args["input"].(models.PortfolioInput)), true + + case "Mutation.updateSecurity": + if e.complexity.Mutation.UpdateSecurity == nil { + break + } + + args, err := ec.field_Mutation_updateSecurity_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateSecurity(childComplexity, args["id"].(string), args["input"].(models.SecurityInput)), true + + case "Mutation.updateTransaction": + if e.complexity.Mutation.UpdateTransaction == nil { + break + } + + args, err := ec.field_Mutation_updateTransaction_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateTransaction(childComplexity, args["id"].(string), args["input"].(models.TransactionInput)), true + + case "Portfolio.accounts": + if e.complexity.Portfolio.Accounts == nil { + break + } + + return e.complexity.Portfolio.Accounts(childComplexity), true + + case "Portfolio.displayName": + if e.complexity.Portfolio.DisplayName == nil { + break + } + + return e.complexity.Portfolio.DisplayName(childComplexity), true + + case "Portfolio.events": + if e.complexity.Portfolio.Events == nil { + break + } + + return e.complexity.Portfolio.Events(childComplexity), true + + case "Portfolio.id": + if e.complexity.Portfolio.ID == nil { + break + } + + return e.complexity.Portfolio.ID(childComplexity), true + + case "Portfolio.snapshot": + if e.complexity.Portfolio.Snapshot == nil { + break + } + + args, err := ec.field_Portfolio_snapshot_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Portfolio.Snapshot(childComplexity, args["when"].(*time.Time)), true + + case "PortfolioEvent.security": + if e.complexity.PortfolioEvent.Security == nil { + break + } + + return e.complexity.PortfolioEvent.Security(childComplexity), true + + case "PortfolioEvent.time": + if e.complexity.PortfolioEvent.Time == nil { + break + } + + return e.complexity.PortfolioEvent.Time(childComplexity), true + + case "PortfolioEvent.type": + if e.complexity.PortfolioEvent.Type == nil { + break + } + + return e.complexity.PortfolioEvent.Type(childComplexity), true + + case "PortfolioPosition.amount": + if e.complexity.PortfolioPosition.Amount == nil { + break + } + + return e.complexity.PortfolioPosition.Amount(childComplexity), true + + case "PortfolioPosition.gains": + if e.complexity.PortfolioPosition.Gains == nil { + break + } + + return e.complexity.PortfolioPosition.Gains(childComplexity), true + + case "PortfolioPosition.marketPrice": + if e.complexity.PortfolioPosition.MarketPrice == nil { + break + } + + return e.complexity.PortfolioPosition.MarketPrice(childComplexity), true + + case "PortfolioPosition.marketValue": + if e.complexity.PortfolioPosition.MarketValue == nil { + break + } + + return e.complexity.PortfolioPosition.MarketValue(childComplexity), true + + case "PortfolioPosition.profitOrLoss": + if e.complexity.PortfolioPosition.ProfitOrLoss == nil { + break + } + + return e.complexity.PortfolioPosition.ProfitOrLoss(childComplexity), true + + case "PortfolioPosition.purchasePrice": + if e.complexity.PortfolioPosition.PurchasePrice == nil { + break + } + + return e.complexity.PortfolioPosition.PurchasePrice(childComplexity), true + + case "PortfolioPosition.purchaseValue": + if e.complexity.PortfolioPosition.PurchaseValue == nil { + break + } + + return e.complexity.PortfolioPosition.PurchaseValue(childComplexity), true + + case "PortfolioPosition.security": + if e.complexity.PortfolioPosition.Security == nil { + break + } + + return e.complexity.PortfolioPosition.Security(childComplexity), true + + case "PortfolioPosition.totalFees": + if e.complexity.PortfolioPosition.TotalFees == nil { + break + } + + return e.complexity.PortfolioPosition.TotalFees(childComplexity), true + + case "PortfolioSnapshot.cash": + if e.complexity.PortfolioSnapshot.Cash == nil { + break + } + + return e.complexity.PortfolioSnapshot.Cash(childComplexity), true + + case "PortfolioSnapshot.firstTransactionTime": + if e.complexity.PortfolioSnapshot.FirstTransactionTime == nil { + break + } + + return e.complexity.PortfolioSnapshot.FirstTransactionTime(childComplexity), true + + case "PortfolioSnapshot.positions": + if e.complexity.PortfolioSnapshot.Positions == nil { + break + } + + return e.complexity.PortfolioSnapshot.Positions(childComplexity), true + + case "PortfolioSnapshot.time": + if e.complexity.PortfolioSnapshot.Time == nil { + break + } + + return e.complexity.PortfolioSnapshot.Time(childComplexity), true + + case "PortfolioSnapshot.totalGains": + if e.complexity.PortfolioSnapshot.TotalGains == nil { + break + } + + return e.complexity.PortfolioSnapshot.TotalGains(childComplexity), true + + case "PortfolioSnapshot.totalMarketValue": + if e.complexity.PortfolioSnapshot.TotalMarketValue == nil { + break + } + + return e.complexity.PortfolioSnapshot.TotalMarketValue(childComplexity), true + + case "PortfolioSnapshot.totalPortfolioValue": + if e.complexity.PortfolioSnapshot.TotalPortfolioValue == nil { + break + } + + return e.complexity.PortfolioSnapshot.TotalPortfolioValue(childComplexity), true + + case "PortfolioSnapshot.totalProfitOrLoss": + if e.complexity.PortfolioSnapshot.TotalProfitOrLoss == nil { + break + } + + return e.complexity.PortfolioSnapshot.TotalProfitOrLoss(childComplexity), true + + case "PortfolioSnapshot.totalPurchaseValue": + if e.complexity.PortfolioSnapshot.TotalPurchaseValue == nil { + break + } + + return e.complexity.PortfolioSnapshot.TotalPurchaseValue(childComplexity), true + + case "Query.account": + if e.complexity.Query.Account == nil { + break + } + + args, err := ec.field_Query_account_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Account(childComplexity, args["id"].(string)), true + + case "Query.accounts": + if e.complexity.Query.Accounts == nil { + break + } + + return e.complexity.Query.Accounts(childComplexity), true + + case "Query.portfolio": + if e.complexity.Query.Portfolio == nil { + break + } + + args, err := ec.field_Query_portfolio_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Portfolio(childComplexity, args["id"].(string)), true + + case "Query.portfolios": + if e.complexity.Query.Portfolios == nil { + break + } + + return e.complexity.Query.Portfolios(childComplexity), true + + case "Query.securities": + if e.complexity.Query.Securities == nil { + break + } + + return e.complexity.Query.Securities(childComplexity), true + + case "Query.security": + if e.complexity.Query.Security == nil { + break + } + + args, err := ec.field_Query_security_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Security(childComplexity, args["id"].(string)), true + + case "Query.transactions": + if e.complexity.Query.Transactions == nil { + break + } + + args, err := ec.field_Query_transactions_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Transactions(childComplexity, args["accountID"].(string)), true + + case "Security.displayName": + if e.complexity.Security.DisplayName == nil { + break + } + + return e.complexity.Security.DisplayName(childComplexity), true + + case "Security.id": + if e.complexity.Security.ID == nil { + break + } + + return e.complexity.Security.ID(childComplexity), true + + case "Security.listedAs": + if e.complexity.Security.ListedAs == nil { + break + } + + return e.complexity.Security.ListedAs(childComplexity), true + + case "Security.quoteProvider": + if e.complexity.Security.QuoteProvider == nil { + break + } + + return e.complexity.Security.QuoteProvider(childComplexity), true + + case "Transaction.amount": + if e.complexity.Transaction.Amount == nil { + break + } + + return e.complexity.Transaction.Amount(childComplexity), true + + case "Transaction.destinationAccount": + if e.complexity.Transaction.DestinationAccount == nil { + break + } + + return e.complexity.Transaction.DestinationAccount(childComplexity), true + + case "Transaction.fees": + if e.complexity.Transaction.Fees == nil { + break + } + + return e.complexity.Transaction.Fees(childComplexity), true + + case "Transaction.id": + if e.complexity.Transaction.ID == nil { + break + } + + return e.complexity.Transaction.ID(childComplexity), true + + case "Transaction.price": + if e.complexity.Transaction.Price == nil { + break + } + + return e.complexity.Transaction.Price(childComplexity), true + + case "Transaction.security": + if e.complexity.Transaction.Security == nil { + break + } + + return e.complexity.Transaction.Security(childComplexity), true + + case "Transaction.sourceAccount": + if e.complexity.Transaction.SourceAccount == nil { + break + } + + return e.complexity.Transaction.SourceAccount(childComplexity), true + + case "Transaction.time": + if e.complexity.Transaction.Time == nil { + break + } + + return e.complexity.Transaction.Time(childComplexity), true + + case "Transaction.type": + if e.complexity.Transaction.Type == nil { + break + } + + return e.complexity.Transaction.Type(childComplexity), true + + } + return 0, false +} + +func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { + opCtx := graphql.GetOperationContext(ctx) + ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)} + inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputAccountInput, + ec.unmarshalInputCurrencyInput, + ec.unmarshalInputListedSecurityInput, + ec.unmarshalInputPortfolioInput, + ec.unmarshalInputSecurityInput, + ec.unmarshalInputTransactionInput, + ) + first := true + + switch opCtx.Operation.Operation { + case ast.Query: + return func(ctx context.Context) *graphql.Response { + var response graphql.Response + var data graphql.Marshaler + if first { + first = false + ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) + data = ec._Query(ctx, opCtx.Operation.SelectionSet) + } else { + if atomic.LoadInt32(&ec.pendingDeferred) > 0 { + result := <-ec.deferredResults + atomic.AddInt32(&ec.pendingDeferred, -1) + data = result.Result + response.Path = result.Path + response.Label = result.Label + response.Errors = result.Errors + } else { + return nil + } + } + var buf bytes.Buffer + data.MarshalGQL(&buf) + response.Data = buf.Bytes() + if atomic.LoadInt32(&ec.deferred) > 0 { + hasNext := atomic.LoadInt32(&ec.pendingDeferred) > 0 + response.HasNext = &hasNext + } + + return &response + } + case ast.Mutation: + return func(ctx context.Context) *graphql.Response { + if !first { + return nil + } + first = false + ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) + data := ec._Mutation(ctx, opCtx.Operation.SelectionSet) + var buf bytes.Buffer + data.MarshalGQL(&buf) + + return &graphql.Response{ + Data: buf.Bytes(), + } + } + + default: + return graphql.OneShot(graphql.ErrorResponse(ctx, "unsupported GraphQL operation")) + } +} + +type executionContext struct { + *graphql.OperationContext + *executableSchema + deferred int32 + pendingDeferred int32 + deferredResults chan graphql.DeferredResult +} + +func (ec *executionContext) processDeferredGroup(dg graphql.DeferredGroup) { + atomic.AddInt32(&ec.pendingDeferred, 1) + go func() { + ctx := graphql.WithFreshResponseContext(dg.Context) + dg.FieldSet.Dispatch(ctx) + ds := graphql.DeferredResult{ + Path: dg.Path, + Label: dg.Label, + Result: dg.FieldSet, + Errors: graphql.GetErrors(ctx), + } + // null fields should bubble up + if dg.FieldSet.Invalids > 0 { + ds.Result = graphql.Null + } + ec.deferredResults <- ds + }() +} + +func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { + if ec.DisableIntrospection { + return nil, errors.New("introspection disabled") + } + return introspection.WrapSchema(ec.Schema()), nil +} + +func (ec *executionContext) introspectType(name string) (*introspection.Type, error) { + if ec.DisableIntrospection { + return nil, errors.New("introspection disabled") + } + return introspection.WrapTypeFromDef(ec.Schema(), ec.Schema().Types[name]), nil +} + +//go:embed "schema.graphqls" +var sourcesFS embed.FS + +func sourceData(filename string) string { + data, err := sourcesFS.ReadFile(filename) + if err != nil { + panic(fmt.Sprintf("codegen problem: %s not available", filename)) + } + return string(data) +} + +var sources = []*ast.Source{ + {Name: "schema.graphqls", Input: sourceData("schema.graphqls"), BuiltIn: false}, +} +var parsedSchema = gqlparser.MustLoadSchema(sources...) + +// endregion ************************** generated!.gotpl ************************** + +// region ***************************** args.gotpl ***************************** + +func (ec *executionContext) field_Mutation_createAccount_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_createAccount_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_createAccount_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.AccountInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNAccountInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐAccountInput(ctx, tmp) + } + + var zeroVal models.AccountInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_createPortfolio_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_createPortfolio_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_createPortfolio_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.PortfolioInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNPortfolioInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioInput(ctx, tmp) + } + + var zeroVal models.PortfolioInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_createSecurity_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_createSecurity_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_createSecurity_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.SecurityInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNSecurityInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐSecurityInput(ctx, tmp) + } + + var zeroVal models.SecurityInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_createTransaction_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_createTransaction_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_createTransaction_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.TransactionInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNTransactionInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐTransactionInput(ctx, tmp) + } + + var zeroVal models.TransactionInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_deleteAccount_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_deleteAccount_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_deleteAccount_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_triggerQuoteUpdate_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_triggerQuoteUpdate_argsSecurityIDs(ctx, rawArgs) + if err != nil { + return nil, err + } + args["securityIDs"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_triggerQuoteUpdate_argsSecurityIDs( + ctx context.Context, + rawArgs map[string]any, +) ([]string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("securityIDs")) + if tmp, ok := rawArgs["securityIDs"]; ok { + return ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + } + + var zeroVal []string + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updatePortfolio_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_updatePortfolio_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + arg1, err := ec.field_Mutation_updatePortfolio_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg1 + return args, nil +} +func (ec *executionContext) field_Mutation_updatePortfolio_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNID2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updatePortfolio_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.PortfolioInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNPortfolioInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioInput(ctx, tmp) + } + + var zeroVal models.PortfolioInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updateSecurity_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_updateSecurity_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + arg1, err := ec.field_Mutation_updateSecurity_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg1 + return args, nil +} +func (ec *executionContext) field_Mutation_updateSecurity_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNID2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updateSecurity_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.SecurityInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNSecurityInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐSecurityInput(ctx, tmp) + } + + var zeroVal models.SecurityInput + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updateTransaction_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_updateTransaction_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + arg1, err := ec.field_Mutation_updateTransaction_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg1 + return args, nil +} +func (ec *executionContext) field_Mutation_updateTransaction_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Mutation_updateTransaction_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (models.TransactionInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNTransactionInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐTransactionInput(ctx, tmp) + } + + var zeroVal models.TransactionInput + return zeroVal, nil +} + +func (ec *executionContext) field_Portfolio_snapshot_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Portfolio_snapshot_argsWhen(ctx, rawArgs) + if err != nil { + return nil, err + } + args["when"] = arg0 + return args, nil +} +func (ec *executionContext) field_Portfolio_snapshot_argsWhen( + ctx context.Context, + rawArgs map[string]any, +) (*time.Time, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("when")) + if tmp, ok := rawArgs["when"]; ok { + return ec.unmarshalOTime2ᚖtimeᚐTime(ctx, tmp) + } + + var zeroVal *time.Time + return zeroVal, nil +} + +func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Query___type_argsName(ctx, rawArgs) + if err != nil { + return nil, err + } + args["name"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query___type_argsName( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + if tmp, ok := rawArgs["name"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Query_account_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Query_account_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query_account_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Query_portfolio_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Query_portfolio_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query_portfolio_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Query_security_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Query_security_argsID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query_security_argsID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + if tmp, ok := rawArgs["id"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field_Query_transactions_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Query_transactions_argsAccountID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["accountID"] = arg0 + return args, nil +} +func (ec *executionContext) field_Query_transactions_argsAccountID( + ctx context.Context, + rawArgs map[string]any, +) (string, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("accountID")) + if tmp, ok := rawArgs["accountID"]; ok { + return ec.unmarshalNString2string(ctx, tmp) + } + + var zeroVal string + return zeroVal, nil +} + +func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field___Type_enumValues_argsIncludeDeprecated(ctx, rawArgs) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil +} +func (ec *executionContext) field___Type_enumValues_argsIncludeDeprecated( + ctx context.Context, + rawArgs map[string]any, +) (bool, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) + if tmp, ok := rawArgs["includeDeprecated"]; ok { + return ec.unmarshalOBoolean2bool(ctx, tmp) + } + + var zeroVal bool + return zeroVal, nil +} + +func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field___Type_fields_argsIncludeDeprecated(ctx, rawArgs) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil +} +func (ec *executionContext) field___Type_fields_argsIncludeDeprecated( + ctx context.Context, + rawArgs map[string]any, +) (bool, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) + if tmp, ok := rawArgs["includeDeprecated"]; ok { + return ec.unmarshalOBoolean2bool(ctx, tmp) + } + + var zeroVal bool + return zeroVal, nil +} + +// endregion ***************************** args.gotpl ***************************** + +// region ************************** directives.gotpl ************************** + +// endregion ************************** directives.gotpl ************************** + +// region **************************** field.gotpl ***************************** + +func (ec *executionContext) _Account_id(ctx context.Context, field graphql.CollectedField, obj *persistence.Account) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Account_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Account_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Account", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Account_displayName(ctx context.Context, field graphql.CollectedField, obj *persistence.Account) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Account_displayName(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisplayName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Account_displayName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Account", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Account_type(ctx context.Context, field graphql.CollectedField, obj *persistence.Account) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Account_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(accounts.AccountType) + fc.Result = res + return ec.marshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Account_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Account", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type AccountType does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Account_referenceAccount(ctx context.Context, field graphql.CollectedField, obj *persistence.Account) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Account_referenceAccount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Account().ReferenceAccount(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalOAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Account_referenceAccount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Account", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Currency_amount(ctx context.Context, field graphql.CollectedField, obj *currency.Currency) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Currency_amount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Amount, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int32) + fc.Result = res + return ec.marshalNInt2int32(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Currency_amount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Currency", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Currency_symbol(ctx context.Context, field graphql.CollectedField, obj *currency.Currency) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Currency_symbol(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Symbol, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Currency_symbol(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Currency", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ListedSecurity_ticker(ctx context.Context, field graphql.CollectedField, obj *persistence.ListedSecurity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ListedSecurity_ticker(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Ticker, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ListedSecurity_ticker(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ListedSecurity", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ListedSecurity_currency(ctx context.Context, field graphql.CollectedField, obj *persistence.ListedSecurity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ListedSecurity_currency(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Currency, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ListedSecurity_currency(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ListedSecurity", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ListedSecurity_security(ctx context.Context, field graphql.CollectedField, obj *persistence.ListedSecurity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ListedSecurity_security(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.ListedSecurity().Security(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ListedSecurity_security(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ListedSecurity", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _ListedSecurity_latestQuote(ctx context.Context, field graphql.CollectedField, obj *persistence.ListedSecurity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ListedSecurity_latestQuote(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.LatestQuote, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalOCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ListedSecurity_latestQuote(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ListedSecurity", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _ListedSecurity_latestQuoteTimestamp(ctx context.Context, field graphql.CollectedField, obj *persistence.ListedSecurity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ListedSecurity_latestQuoteTimestamp(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.LatestQuoteTimestamp, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*time.Time) + fc.Result = res + return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ListedSecurity_latestQuoteTimestamp(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ListedSecurity", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createSecurity(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createSecurity(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateSecurity(rctx, fc.Args["input"].(models.SecurityInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createSecurity(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createSecurity_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_updateSecurity(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updateSecurity(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateSecurity(rctx, fc.Args["id"].(string), fc.Args["input"].(models.SecurityInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_updateSecurity(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_updateSecurity_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createPortfolio(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createPortfolio(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreatePortfolio(rctx, fc.Args["input"].(models.PortfolioInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Portfolio) + fc.Result = res + return ec.marshalNPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createPortfolio(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Portfolio_id(ctx, field) + case "displayName": + return ec.fieldContext_Portfolio_displayName(ctx, field) + case "accounts": + return ec.fieldContext_Portfolio_accounts(ctx, field) + case "snapshot": + return ec.fieldContext_Portfolio_snapshot(ctx, field) + case "events": + return ec.fieldContext_Portfolio_events(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Portfolio", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createPortfolio_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_updatePortfolio(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updatePortfolio(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdatePortfolio(rctx, fc.Args["id"].(string), fc.Args["input"].(models.PortfolioInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Portfolio) + fc.Result = res + return ec.marshalNPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_updatePortfolio(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Portfolio_id(ctx, field) + case "displayName": + return ec.fieldContext_Portfolio_displayName(ctx, field) + case "accounts": + return ec.fieldContext_Portfolio_accounts(ctx, field) + case "snapshot": + return ec.fieldContext_Portfolio_snapshot(ctx, field) + case "events": + return ec.fieldContext_Portfolio_events(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Portfolio", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_updatePortfolio_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createAccount(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createAccount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateAccount(rctx, fc.Args["input"].(models.AccountInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createAccount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createAccount_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_deleteAccount(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_deleteAccount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteAccount(rctx, fc.Args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_deleteAccount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_deleteAccount_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createTransaction(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createTransaction(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateTransaction(rctx, fc.Args["input"].(models.TransactionInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Transaction) + fc.Result = res + return ec.marshalNTransaction2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransaction(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createTransaction(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Transaction_id(ctx, field) + case "time": + return ec.fieldContext_Transaction_time(ctx, field) + case "sourceAccount": + return ec.fieldContext_Transaction_sourceAccount(ctx, field) + case "destinationAccount": + return ec.fieldContext_Transaction_destinationAccount(ctx, field) + case "security": + return ec.fieldContext_Transaction_security(ctx, field) + case "amount": + return ec.fieldContext_Transaction_amount(ctx, field) + case "price": + return ec.fieldContext_Transaction_price(ctx, field) + case "fees": + return ec.fieldContext_Transaction_fees(ctx, field) + case "type": + return ec.fieldContext_Transaction_type(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Transaction", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createTransaction_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_updateTransaction(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updateTransaction(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateTransaction(rctx, fc.Args["id"].(string), fc.Args["input"].(models.TransactionInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Transaction) + fc.Result = res + return ec.marshalNTransaction2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransaction(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_updateTransaction(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Transaction_id(ctx, field) + case "time": + return ec.fieldContext_Transaction_time(ctx, field) + case "sourceAccount": + return ec.fieldContext_Transaction_sourceAccount(ctx, field) + case "destinationAccount": + return ec.fieldContext_Transaction_destinationAccount(ctx, field) + case "security": + return ec.fieldContext_Transaction_security(ctx, field) + case "amount": + return ec.fieldContext_Transaction_amount(ctx, field) + case "price": + return ec.fieldContext_Transaction_price(ctx, field) + case "fees": + return ec.fieldContext_Transaction_fees(ctx, field) + case "type": + return ec.fieldContext_Transaction_type(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Transaction", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_updateTransaction_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_triggerQuoteUpdate(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_triggerQuoteUpdate(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().TriggerQuoteUpdate(rctx, fc.Args["securityIDs"].([]string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.ListedSecurity) + fc.Result = res + return ec.marshalNListedSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_triggerQuoteUpdate(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "ticker": + return ec.fieldContext_ListedSecurity_ticker(ctx, field) + case "currency": + return ec.fieldContext_ListedSecurity_currency(ctx, field) + case "security": + return ec.fieldContext_ListedSecurity_security(ctx, field) + case "latestQuote": + return ec.fieldContext_ListedSecurity_latestQuote(ctx, field) + case "latestQuoteTimestamp": + return ec.fieldContext_ListedSecurity_latestQuoteTimestamp(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ListedSecurity", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_triggerQuoteUpdate_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Portfolio_id(ctx context.Context, field graphql.CollectedField, obj *persistence.Portfolio) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Portfolio_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Portfolio_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Portfolio", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Portfolio_displayName(ctx context.Context, field graphql.CollectedField, obj *persistence.Portfolio) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Portfolio_displayName(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisplayName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Portfolio_displayName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Portfolio", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Portfolio_accounts(ctx context.Context, field graphql.CollectedField, obj *persistence.Portfolio) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Portfolio_accounts(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Portfolio().Accounts(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccountᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Portfolio_accounts(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Portfolio", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Portfolio_snapshot(ctx context.Context, field graphql.CollectedField, obj *persistence.Portfolio) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Portfolio_snapshot(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Portfolio().Snapshot(rctx, obj, fc.Args["when"].(*time.Time)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.PortfolioSnapshot) + fc.Result = res + return ec.marshalOPortfolioSnapshot2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioSnapshot(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Portfolio_snapshot(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Portfolio", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "time": + return ec.fieldContext_PortfolioSnapshot_time(ctx, field) + case "positions": + return ec.fieldContext_PortfolioSnapshot_positions(ctx, field) + case "firstTransactionTime": + return ec.fieldContext_PortfolioSnapshot_firstTransactionTime(ctx, field) + case "totalPurchaseValue": + return ec.fieldContext_PortfolioSnapshot_totalPurchaseValue(ctx, field) + case "totalMarketValue": + return ec.fieldContext_PortfolioSnapshot_totalMarketValue(ctx, field) + case "totalProfitOrLoss": + return ec.fieldContext_PortfolioSnapshot_totalProfitOrLoss(ctx, field) + case "totalGains": + return ec.fieldContext_PortfolioSnapshot_totalGains(ctx, field) + case "totalPortfolioValue": + return ec.fieldContext_PortfolioSnapshot_totalPortfolioValue(ctx, field) + case "cash": + return ec.fieldContext_PortfolioSnapshot_cash(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PortfolioSnapshot", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Portfolio_snapshot_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Portfolio_events(ctx context.Context, field graphql.CollectedField, obj *persistence.Portfolio) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Portfolio_events(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Portfolio().Events(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*models.PortfolioEvent) + fc.Result = res + return ec.marshalNPortfolioEvent2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioEventᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Portfolio_events(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Portfolio", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "time": + return ec.fieldContext_PortfolioEvent_time(ctx, field) + case "type": + return ec.fieldContext_PortfolioEvent_type(ctx, field) + case "security": + return ec.fieldContext_PortfolioEvent_security(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PortfolioEvent", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioEvent_time(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioEvent_time(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Time, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioEvent_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioEvent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioEvent_type(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioEvent_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(events.PortfolioEventType) + fc.Result = res + return ec.marshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioEvent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type PortfolioEventType does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioEvent_security(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioEvent_security(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Security, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalOSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioEvent_security(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioEvent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_security(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_security(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Security, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_security(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_amount(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_amount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Amount, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_amount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_purchaseValue(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_purchaseValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.PurchaseValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_purchaseValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_purchasePrice(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_purchasePrice(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.PurchasePrice, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_purchasePrice(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_marketValue(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_marketValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.MarketValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_marketValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_marketPrice(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_marketPrice(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.MarketPrice, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_marketPrice(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_totalFees(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_totalFees(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalFees, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_totalFees(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_profitOrLoss(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_profitOrLoss(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ProfitOrLoss, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_profitOrLoss(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioPosition_gains(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioPosition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioPosition_gains(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Gains, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioPosition_gains(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioPosition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_time(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_time(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Time, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_positions(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_positions(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Positions, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*models.PortfolioPosition) + fc.Result = res + return ec.marshalNPortfolioPosition2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioPositionᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_positions(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "security": + return ec.fieldContext_PortfolioPosition_security(ctx, field) + case "amount": + return ec.fieldContext_PortfolioPosition_amount(ctx, field) + case "purchaseValue": + return ec.fieldContext_PortfolioPosition_purchaseValue(ctx, field) + case "purchasePrice": + return ec.fieldContext_PortfolioPosition_purchasePrice(ctx, field) + case "marketValue": + return ec.fieldContext_PortfolioPosition_marketValue(ctx, field) + case "marketPrice": + return ec.fieldContext_PortfolioPosition_marketPrice(ctx, field) + case "totalFees": + return ec.fieldContext_PortfolioPosition_totalFees(ctx, field) + case "profitOrLoss": + return ec.fieldContext_PortfolioPosition_profitOrLoss(ctx, field) + case "gains": + return ec.fieldContext_PortfolioPosition_gains(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PortfolioPosition", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_firstTransactionTime(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_firstTransactionTime(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.FirstTransactionTime, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_firstTransactionTime(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_totalPurchaseValue(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_totalPurchaseValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalPurchaseValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_totalPurchaseValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_totalMarketValue(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_totalMarketValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalMarketValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_totalMarketValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_totalProfitOrLoss(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_totalProfitOrLoss(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalProfitOrLoss, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_totalProfitOrLoss(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_totalGains(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_totalGains(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalGains, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_totalGains(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_totalPortfolioValue(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_totalPortfolioValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.TotalPortfolioValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalOCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_totalPortfolioValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _PortfolioSnapshot_cash(ctx context.Context, field graphql.CollectedField, obj *models.PortfolioSnapshot) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_PortfolioSnapshot_cash(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Cash, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_PortfolioSnapshot_cash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "PortfolioSnapshot", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_security(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_security(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Security(rctx, fc.Args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalOSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_security(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_security_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_securities(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_securities(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Securities(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurityᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_securities(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_portfolio(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_portfolio(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Portfolio(rctx, fc.Args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*persistence.Portfolio) + fc.Result = res + return ec.marshalOPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_portfolio(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Portfolio_id(ctx, field) + case "displayName": + return ec.fieldContext_Portfolio_displayName(ctx, field) + case "accounts": + return ec.fieldContext_Portfolio_accounts(ctx, field) + case "snapshot": + return ec.fieldContext_Portfolio_snapshot(ctx, field) + case "events": + return ec.fieldContext_Portfolio_events(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Portfolio", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_portfolio_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_portfolios(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_portfolios(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Portfolios(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.Portfolio) + fc.Result = res + return ec.marshalNPortfolio2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolioᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_portfolios(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Portfolio_id(ctx, field) + case "displayName": + return ec.fieldContext_Portfolio_displayName(ctx, field) + case "accounts": + return ec.fieldContext_Portfolio_accounts(ctx, field) + case "snapshot": + return ec.fieldContext_Portfolio_snapshot(ctx, field) + case "events": + return ec.fieldContext_Portfolio_events(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Portfolio", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_account(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_account(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Account(rctx, fc.Args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalOAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_account(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_account_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_accounts(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_accounts(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Accounts(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccountᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_accounts(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_transactions(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_transactions(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Transactions(rctx, fc.Args["accountID"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*persistence.Transaction) + fc.Result = res + return ec.marshalNTransaction2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransactionᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_transactions(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Transaction_id(ctx, field) + case "time": + return ec.fieldContext_Transaction_time(ctx, field) + case "sourceAccount": + return ec.fieldContext_Transaction_sourceAccount(ctx, field) + case "destinationAccount": + return ec.fieldContext_Transaction_destinationAccount(ctx, field) + case "security": + return ec.fieldContext_Transaction_security(ctx, field) + case "amount": + return ec.fieldContext_Transaction_amount(ctx, field) + case "price": + return ec.fieldContext_Transaction_price(ctx, field) + case "fees": + return ec.fieldContext_Transaction_fees(ctx, field) + case "type": + return ec.fieldContext_Transaction_type(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Transaction", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_transactions_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.introspectType(fc.Args["name"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query___type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query___type_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___schema(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.introspectSchema() + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Schema) + fc.Result = res + return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query___schema(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "description": + return ec.fieldContext___Schema_description(ctx, field) + case "types": + return ec.fieldContext___Schema_types(ctx, field) + case "queryType": + return ec.fieldContext___Schema_queryType(ctx, field) + case "mutationType": + return ec.fieldContext___Schema_mutationType(ctx, field) + case "subscriptionType": + return ec.fieldContext___Schema_subscriptionType(ctx, field) + case "directives": + return ec.fieldContext___Schema_directives(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Schema", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Security_id(ctx context.Context, field graphql.CollectedField, obj *persistence.Security) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Security_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Security_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Security", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Security_displayName(ctx context.Context, field graphql.CollectedField, obj *persistence.Security) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Security_displayName(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisplayName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Security_displayName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Security", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Security_quoteProvider(ctx context.Context, field graphql.CollectedField, obj *persistence.Security) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Security_quoteProvider(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.QuoteProvider, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Security_quoteProvider(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Security", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Security_listedAs(ctx context.Context, field graphql.CollectedField, obj *persistence.Security) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Security_listedAs(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Security().ListedAs(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*persistence.ListedSecurity) + fc.Result = res + return ec.marshalOListedSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurityᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Security_listedAs(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Security", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "ticker": + return ec.fieldContext_ListedSecurity_ticker(ctx, field) + case "currency": + return ec.fieldContext_ListedSecurity_currency(ctx, field) + case "security": + return ec.fieldContext_ListedSecurity_security(ctx, field) + case "latestQuote": + return ec.fieldContext_ListedSecurity_latestQuote(ctx, field) + case "latestQuoteTimestamp": + return ec.fieldContext_ListedSecurity_latestQuoteTimestamp(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ListedSecurity", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_id(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_time(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_time(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Time, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_sourceAccount(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_sourceAccount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Transaction().SourceAccount(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_sourceAccount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_destinationAccount(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_destinationAccount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Transaction().DestinationAccount(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Account) + fc.Result = res + return ec.marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_destinationAccount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Account_id(ctx, field) + case "displayName": + return ec.fieldContext_Account_displayName(ctx, field) + case "type": + return ec.fieldContext_Account_type(ctx, field) + case "referenceAccount": + return ec.fieldContext_Account_referenceAccount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Account", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_security(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_security(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Transaction().Security(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*persistence.Security) + fc.Result = res + return ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_security(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Security_id(ctx, field) + case "displayName": + return ec.fieldContext_Security_displayName(ctx, field) + case "quoteProvider": + return ec.fieldContext_Security_quoteProvider(ctx, field) + case "listedAs": + return ec.fieldContext_Security_listedAs(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Security", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_amount(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_amount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Amount, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_amount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Float does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_price(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_price(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Price, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_price(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_fees(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_fees(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Fees, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*currency.Currency) + fc.Result = res + return ec.marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_fees(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "amount": + return ec.fieldContext_Currency_amount(ctx, field) + case "symbol": + return ec.fieldContext_Currency_symbol(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Currency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Transaction_type(ctx context.Context, field graphql.CollectedField, obj *persistence.Transaction) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Transaction_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(events.PortfolioEventType) + fc.Result = res + return ec.marshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Transaction_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Transaction", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type PortfolioEventType does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_locations(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_locations(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Locations, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalN__DirectiveLocation2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_locations(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type __DirectiveLocation does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_args(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Args, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_args(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Directive_isRepeatable(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Directive_isRepeatable(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsRepeatable, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Directive_isRepeatable(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Directive", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_isDeprecated(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsDeprecated(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_isDeprecated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.EnumValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___EnumValue_deprecationReason(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeprecationReason(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___EnumValue_deprecationReason(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__EnumValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_args(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Args, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_args(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_isDeprecated(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsDeprecated(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_isDeprecated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, field graphql.CollectedField, obj *introspection.Field) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Field_deprecationReason(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeprecationReason(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Field_deprecationReason(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Field", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_description(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, field graphql.CollectedField, obj *introspection.InputValue) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___InputValue_defaultValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.DefaultValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___InputValue_defaultValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__InputValue", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_types(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Types(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_types(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_queryType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.QueryType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_queryType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_mutationType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.MutationType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_mutationType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_subscriptionType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.SubscriptionType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_subscriptionType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Schema_directives(ctx context.Context, field graphql.CollectedField, obj *introspection.Schema) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Schema_directives(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Directives(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]introspection.Directive) + fc.Result = res + return ec.marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirectiveᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Schema_directives(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Schema", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___Directive_name(ctx, field) + case "description": + return ec.fieldContext___Directive_description(ctx, field) + case "locations": + return ec.fieldContext___Directive_locations(ctx, field) + case "args": + return ec.fieldContext___Directive_args(ctx, field) + case "isRepeatable": + return ec.fieldContext___Directive_isRepeatable(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Directive", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_kind(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Kind(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalN__TypeKind2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_kind(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type __TypeKind does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_description(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_description(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Description(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_fields(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Fields(fc.Args["includeDeprecated"].(bool)), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Field) + fc.Result = res + return ec.marshalO__Field2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐFieldᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_fields(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___Field_name(ctx, field) + case "description": + return ec.fieldContext___Field_description(ctx, field) + case "args": + return ec.fieldContext___Field_args(ctx, field) + case "type": + return ec.fieldContext___Field_type(ctx, field) + case "isDeprecated": + return ec.fieldContext___Field_isDeprecated(ctx, field) + case "deprecationReason": + return ec.fieldContext___Field_deprecationReason(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Field", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field___Type_fields_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_interfaces(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Interfaces(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_interfaces(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_possibleTypes(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.PossibleTypes(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_possibleTypes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_enumValues(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnumValues(fc.Args["includeDeprecated"].(bool)), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.EnumValue) + fc.Result = res + return ec.marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_enumValues(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___EnumValue_name(ctx, field) + case "description": + return ec.fieldContext___EnumValue_description(ctx, field) + case "isDeprecated": + return ec.fieldContext___EnumValue_isDeprecated(ctx, field) + case "deprecationReason": + return ec.fieldContext___EnumValue_deprecationReason(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __EnumValue", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field___Type_enumValues_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_inputFields(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.InputFields(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]introspection.InputValue) + fc.Result = res + return ec.marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_inputFields(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext___InputValue_name(ctx, field) + case "description": + return ec.fieldContext___InputValue_description(ctx, field) + case "type": + return ec.fieldContext___InputValue_type(ctx, field) + case "defaultValue": + return ec.fieldContext___InputValue_defaultValue(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __InputValue", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_ofType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.OfType(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_ofType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) ___Type_specifiedByURL(ctx context.Context, field graphql.CollectedField, obj *introspection.Type) (ret graphql.Marshaler) { + fc, err := ec.fieldContext___Type_specifiedByURL(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.SpecifiedByURL(), nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext___Type_specifiedByURL(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "__Type", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +// endregion **************************** field.gotpl ***************************** + +// region **************************** input.gotpl ***************************** + +func (ec *executionContext) unmarshalInputAccountInput(ctx context.Context, obj any) (models.AccountInput, error) { + var it models.AccountInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "displayName", "type"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "displayName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("displayName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.DisplayName = data + case "type": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) + data, err := ec.unmarshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType(ctx, v) + if err != nil { + return it, err + } + it.Type = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputCurrencyInput(ctx context.Context, obj any) (models.CurrencyInput, error) { + var it models.CurrencyInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"amount", "symbol"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "amount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("amount")) + data, err := ec.unmarshalNInt2int(ctx, v) + if err != nil { + return it, err + } + it.Amount = data + case "symbol": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("symbol")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Symbol = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputListedSecurityInput(ctx context.Context, obj any) (models.ListedSecurityInput, error) { + var it models.ListedSecurityInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"ticker", "currency"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "ticker": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ticker")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Ticker = data + case "currency": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("currency")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Currency = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputPortfolioInput(ctx context.Context, obj any) (models.PortfolioInput, error) { + var it models.PortfolioInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "displayName", "accountIds"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "displayName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("displayName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.DisplayName = data + case "accountIds": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("accountIds")) + data, err := ec.unmarshalNString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.AccountIds = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputSecurityInput(ctx context.Context, obj any) (models.SecurityInput, error) { + var it models.SecurityInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "displayName", "listedAs"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "displayName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("displayName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.DisplayName = data + case "listedAs": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("listedAs")) + data, err := ec.unmarshalOListedSecurityInput2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐListedSecurityInputᚄ(ctx, v) + if err != nil { + return it, err + } + it.ListedAs = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputTransactionInput(ctx context.Context, obj any) (models.TransactionInput, error) { + var it models.TransactionInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"time", "sourceAccountID", "destinationAccountID", "securityID", "amount", "price", "fees", "taxes", "type"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "time": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("time")) + data, err := ec.unmarshalNTime2timeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.Time = data + case "sourceAccountID": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sourceAccountID")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.SourceAccountID = data + case "destinationAccountID": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("destinationAccountID")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.DestinationAccountID = data + case "securityID": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("securityID")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.SecurityID = data + case "amount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("amount")) + data, err := ec.unmarshalNFloat2float64(ctx, v) + if err != nil { + return it, err + } + it.Amount = data + case "price": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("price")) + data, err := ec.unmarshalNCurrencyInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐCurrencyInput(ctx, v) + if err != nil { + return it, err + } + it.Price = data + case "fees": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fees")) + data, err := ec.unmarshalNCurrencyInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐCurrencyInput(ctx, v) + if err != nil { + return it, err + } + it.Fees = data + case "taxes": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("taxes")) + data, err := ec.unmarshalNCurrencyInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐCurrencyInput(ctx, v) + if err != nil { + return it, err + } + it.Taxes = data + case "type": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) + data, err := ec.unmarshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType(ctx, v) + if err != nil { + return it, err + } + it.Type = data + } + } + + return it, nil +} + +// endregion **************************** input.gotpl ***************************** + +// region ************************** interface.gotpl *************************** + +// endregion ************************** interface.gotpl *************************** + +// region **************************** object.gotpl **************************** + +var accountImplementors = []string{"Account"} + +func (ec *executionContext) _Account(ctx context.Context, sel ast.SelectionSet, obj *persistence.Account) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, accountImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Account") + case "id": + out.Values[i] = ec._Account_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "displayName": + out.Values[i] = ec._Account_displayName(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "type": + out.Values[i] = ec._Account_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "referenceAccount": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Account_referenceAccount(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var currencyImplementors = []string{"Currency"} + +func (ec *executionContext) _Currency(ctx context.Context, sel ast.SelectionSet, obj *currency.Currency) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, currencyImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Currency") + case "amount": + out.Values[i] = ec._Currency_amount(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "symbol": + out.Values[i] = ec._Currency_symbol(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var listedSecurityImplementors = []string{"ListedSecurity"} + +func (ec *executionContext) _ListedSecurity(ctx context.Context, sel ast.SelectionSet, obj *persistence.ListedSecurity) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, listedSecurityImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ListedSecurity") + case "ticker": + out.Values[i] = ec._ListedSecurity_ticker(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "currency": + out.Values[i] = ec._ListedSecurity_currency(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "security": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ListedSecurity_security(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "latestQuote": + out.Values[i] = ec._ListedSecurity_latestQuote(ctx, field, obj) + case "latestQuoteTimestamp": + out.Values[i] = ec._ListedSecurity_latestQuoteTimestamp(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var mutationImplementors = []string{"Mutation"} + +func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mutationImplementors) + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ + Object: "Mutation", + }) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{ + Object: field.Name, + Field: field, + }) + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Mutation") + case "createSecurity": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createSecurity(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updateSecurity": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updateSecurity(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createPortfolio": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createPortfolio(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatePortfolio": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updatePortfolio(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createAccount": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createAccount(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deleteAccount": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_deleteAccount(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createTransaction": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createTransaction(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updateTransaction": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updateTransaction(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "triggerQuoteUpdate": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_triggerQuoteUpdate(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var portfolioImplementors = []string{"Portfolio"} + +func (ec *executionContext) _Portfolio(ctx context.Context, sel ast.SelectionSet, obj *persistence.Portfolio) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, portfolioImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Portfolio") + case "id": + out.Values[i] = ec._Portfolio_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "displayName": + out.Values[i] = ec._Portfolio_displayName(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "accounts": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Portfolio_accounts(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "snapshot": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Portfolio_snapshot(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "events": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Portfolio_events(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var portfolioEventImplementors = []string{"PortfolioEvent"} + +func (ec *executionContext) _PortfolioEvent(ctx context.Context, sel ast.SelectionSet, obj *models.PortfolioEvent) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, portfolioEventImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("PortfolioEvent") + case "time": + out.Values[i] = ec._PortfolioEvent_time(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "type": + out.Values[i] = ec._PortfolioEvent_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "security": + out.Values[i] = ec._PortfolioEvent_security(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var portfolioPositionImplementors = []string{"PortfolioPosition"} + +func (ec *executionContext) _PortfolioPosition(ctx context.Context, sel ast.SelectionSet, obj *models.PortfolioPosition) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, portfolioPositionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("PortfolioPosition") + case "security": + out.Values[i] = ec._PortfolioPosition_security(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "amount": + out.Values[i] = ec._PortfolioPosition_amount(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "purchaseValue": + out.Values[i] = ec._PortfolioPosition_purchaseValue(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "purchasePrice": + out.Values[i] = ec._PortfolioPosition_purchasePrice(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "marketValue": + out.Values[i] = ec._PortfolioPosition_marketValue(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "marketPrice": + out.Values[i] = ec._PortfolioPosition_marketPrice(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalFees": + out.Values[i] = ec._PortfolioPosition_totalFees(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "profitOrLoss": + out.Values[i] = ec._PortfolioPosition_profitOrLoss(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "gains": + out.Values[i] = ec._PortfolioPosition_gains(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var portfolioSnapshotImplementors = []string{"PortfolioSnapshot"} + +func (ec *executionContext) _PortfolioSnapshot(ctx context.Context, sel ast.SelectionSet, obj *models.PortfolioSnapshot) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, portfolioSnapshotImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("PortfolioSnapshot") + case "time": + out.Values[i] = ec._PortfolioSnapshot_time(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "positions": + out.Values[i] = ec._PortfolioSnapshot_positions(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "firstTransactionTime": + out.Values[i] = ec._PortfolioSnapshot_firstTransactionTime(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalPurchaseValue": + out.Values[i] = ec._PortfolioSnapshot_totalPurchaseValue(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalMarketValue": + out.Values[i] = ec._PortfolioSnapshot_totalMarketValue(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalProfitOrLoss": + out.Values[i] = ec._PortfolioSnapshot_totalProfitOrLoss(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalGains": + out.Values[i] = ec._PortfolioSnapshot_totalGains(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "totalPortfolioValue": + out.Values[i] = ec._PortfolioSnapshot_totalPortfolioValue(ctx, field, obj) + case "cash": + out.Values[i] = ec._PortfolioSnapshot_cash(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var queryImplementors = []string{"Query"} + +func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, queryImplementors) + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ + Object: "Query", + }) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{ + Object: field.Name, + Field: field, + }) + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Query") + case "security": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_security(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "securities": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_securities(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "portfolio": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_portfolio(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "portfolios": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_portfolios(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "account": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_account(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "accounts": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_accounts(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "transactions": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_transactions(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "__type": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Query___type(ctx, field) + }) + case "__schema": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Query___schema(ctx, field) + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var securityImplementors = []string{"Security"} + +func (ec *executionContext) _Security(ctx context.Context, sel ast.SelectionSet, obj *persistence.Security) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, securityImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Security") + case "id": + out.Values[i] = ec._Security_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "displayName": + out.Values[i] = ec._Security_displayName(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "quoteProvider": + out.Values[i] = ec._Security_quoteProvider(ctx, field, obj) + case "listedAs": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Security_listedAs(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var transactionImplementors = []string{"Transaction"} + +func (ec *executionContext) _Transaction(ctx context.Context, sel ast.SelectionSet, obj *persistence.Transaction) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, transactionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Transaction") + case "id": + out.Values[i] = ec._Transaction_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "time": + out.Values[i] = ec._Transaction_time(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "sourceAccount": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Transaction_sourceAccount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "destinationAccount": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Transaction_destinationAccount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "security": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Transaction_security(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "amount": + out.Values[i] = ec._Transaction_amount(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "price": + out.Values[i] = ec._Transaction_price(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "fees": + out.Values[i] = ec._Transaction_fees(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "type": + out.Values[i] = ec._Transaction_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __DirectiveImplementors = []string{"__Directive"} + +func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __DirectiveImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Directive") + case "name": + out.Values[i] = ec.___Directive_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___Directive_description(ctx, field, obj) + case "locations": + out.Values[i] = ec.___Directive_locations(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "args": + out.Values[i] = ec.___Directive_args(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "isRepeatable": + out.Values[i] = ec.___Directive_isRepeatable(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __EnumValueImplementors = []string{"__EnumValue"} + +func (ec *executionContext) ___EnumValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.EnumValue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __EnumValueImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__EnumValue") + case "name": + out.Values[i] = ec.___EnumValue_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___EnumValue_description(ctx, field, obj) + case "isDeprecated": + out.Values[i] = ec.___EnumValue_isDeprecated(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deprecationReason": + out.Values[i] = ec.___EnumValue_deprecationReason(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __FieldImplementors = []string{"__Field"} + +func (ec *executionContext) ___Field(ctx context.Context, sel ast.SelectionSet, obj *introspection.Field) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __FieldImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Field") + case "name": + out.Values[i] = ec.___Field_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___Field_description(ctx, field, obj) + case "args": + out.Values[i] = ec.___Field_args(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "type": + out.Values[i] = ec.___Field_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "isDeprecated": + out.Values[i] = ec.___Field_isDeprecated(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deprecationReason": + out.Values[i] = ec.___Field_deprecationReason(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __InputValueImplementors = []string{"__InputValue"} + +func (ec *executionContext) ___InputValue(ctx context.Context, sel ast.SelectionSet, obj *introspection.InputValue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __InputValueImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__InputValue") + case "name": + out.Values[i] = ec.___InputValue_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "description": + out.Values[i] = ec.___InputValue_description(ctx, field, obj) + case "type": + out.Values[i] = ec.___InputValue_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "defaultValue": + out.Values[i] = ec.___InputValue_defaultValue(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __SchemaImplementors = []string{"__Schema"} + +func (ec *executionContext) ___Schema(ctx context.Context, sel ast.SelectionSet, obj *introspection.Schema) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __SchemaImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Schema") + case "description": + out.Values[i] = ec.___Schema_description(ctx, field, obj) + case "types": + out.Values[i] = ec.___Schema_types(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "queryType": + out.Values[i] = ec.___Schema_queryType(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "mutationType": + out.Values[i] = ec.___Schema_mutationType(ctx, field, obj) + case "subscriptionType": + out.Values[i] = ec.___Schema_subscriptionType(ctx, field, obj) + case "directives": + out.Values[i] = ec.___Schema_directives(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var __TypeImplementors = []string{"__Type"} + +func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, obj *introspection.Type) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, __TypeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("__Type") + case "kind": + out.Values[i] = ec.___Type_kind(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "name": + out.Values[i] = ec.___Type_name(ctx, field, obj) + case "description": + out.Values[i] = ec.___Type_description(ctx, field, obj) + case "fields": + out.Values[i] = ec.___Type_fields(ctx, field, obj) + case "interfaces": + out.Values[i] = ec.___Type_interfaces(ctx, field, obj) + case "possibleTypes": + out.Values[i] = ec.___Type_possibleTypes(ctx, field, obj) + case "enumValues": + out.Values[i] = ec.___Type_enumValues(ctx, field, obj) + case "inputFields": + out.Values[i] = ec.___Type_inputFields(ctx, field, obj) + case "ofType": + out.Values[i] = ec.___Type_ofType(ctx, field, obj) + case "specifiedByURL": + out.Values[i] = ec.___Type_specifiedByURL(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +// endregion **************************** object.gotpl **************************** + +// region ***************************** type.gotpl ***************************** + +func (ec *executionContext) marshalNAccount2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx context.Context, sel ast.SelectionSet, v persistence.Account) graphql.Marshaler { + return ec._Account(ctx, sel, &v) +} + +func (ec *executionContext) marshalNAccount2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccountᚄ(ctx context.Context, sel ast.SelectionSet, v []*persistence.Account) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx context.Context, sel ast.SelectionSet, v *persistence.Account) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Account(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNAccountInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐAccountInput(ctx context.Context, v any) (models.AccountInput, error) { + res, err := ec.unmarshalInputAccountInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType(ctx context.Context, v any) (accounts.AccountType, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType(ctx context.Context, sel ast.SelectionSet, v accounts.AccountType) graphql.Marshaler { + res := graphql.MarshalString(marshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType = map[string]accounts.AccountType{ + "BROKERAGE": accounts.AccountTypeBrokerage, + "BANK": accounts.AccountTypeBank, + "LOAN": accounts.AccountTypeLoan, + } + marshalNAccountType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋaccountsᚐAccountType = map[accounts.AccountType]string{ + accounts.AccountTypeBrokerage: "BROKERAGE", + accounts.AccountTypeBank: "BANK", + accounts.AccountTypeLoan: "LOAN", + } +) + +func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v any) (bool, error) { + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { + res := graphql.MarshalBoolean(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalNCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx context.Context, sel ast.SelectionSet, v *currency.Currency) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Currency(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNCurrencyInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐCurrencyInput(ctx context.Context, v any) (*models.CurrencyInput, error) { + res, err := ec.unmarshalInputCurrencyInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v any) (float64, error) { + res, err := graphql.UnmarshalFloatContext(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.SelectionSet, v float64) graphql.Marshaler { + res := graphql.MarshalFloatContext(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return graphql.WrapContextMarshaler(ctx, res) +} + +func (ec *executionContext) unmarshalNID2string(ctx context.Context, v any) (string, error) { + res, err := graphql.UnmarshalID(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalID(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v any) (int, error) { + res, err := graphql.UnmarshalInt(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { + res := graphql.MarshalInt(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNInt2int32(ctx context.Context, v any) (int32, error) { + res, err := graphql.UnmarshalInt32(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNInt2int32(ctx context.Context, sel ast.SelectionSet, v int32) graphql.Marshaler { + res := graphql.MarshalInt32(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalNListedSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx context.Context, sel ast.SelectionSet, v []*persistence.ListedSecurity) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOListedSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + return ret +} + +func (ec *executionContext) marshalNListedSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx context.Context, sel ast.SelectionSet, v *persistence.ListedSecurity) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._ListedSecurity(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNListedSecurityInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐListedSecurityInput(ctx context.Context, v any) (*models.ListedSecurityInput, error) { + res, err := ec.unmarshalInputListedSecurityInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNPortfolio2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx context.Context, sel ast.SelectionSet, v persistence.Portfolio) graphql.Marshaler { + return ec._Portfolio(ctx, sel, &v) +} + +func (ec *executionContext) marshalNPortfolio2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolioᚄ(ctx context.Context, sel ast.SelectionSet, v []*persistence.Portfolio) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx context.Context, sel ast.SelectionSet, v *persistence.Portfolio) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Portfolio(ctx, sel, v) +} + +func (ec *executionContext) marshalNPortfolioEvent2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioEventᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.PortfolioEvent) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNPortfolioEvent2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioEvent(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNPortfolioEvent2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioEvent(ctx context.Context, sel ast.SelectionSet, v *models.PortfolioEvent) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._PortfolioEvent(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType(ctx context.Context, v any) (events.PortfolioEventType, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType(ctx context.Context, sel ast.SelectionSet, v events.PortfolioEventType) graphql.Marshaler { + res := graphql.MarshalString(marshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType = map[string]events.PortfolioEventType{ + "BUY": events.PortfolioEventTypeBuy, + "SELL": events.PortfolioEventTypeSell, + "DIVIDEND": events.PortfolioEventTypeDividend, + "DELIVERY_INBOUND": events.PortfolioEventTypeDeliveryInbound, + "DELIVERY_OUTBOUND": events.PortfolioEventTypeDeliveryOutbound, + "DEPOSIT_CASH": events.PortfolioEventTypeDepositCash, + "WITHDRAW_CASH": events.PortfolioEventTypeWithdrawCash, + } + marshalNPortfolioEventType2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋportfolioᚋeventsᚐPortfolioEventType = map[events.PortfolioEventType]string{ + events.PortfolioEventTypeBuy: "BUY", + events.PortfolioEventTypeSell: "SELL", + events.PortfolioEventTypeDividend: "DIVIDEND", + events.PortfolioEventTypeDeliveryInbound: "DELIVERY_INBOUND", + events.PortfolioEventTypeDeliveryOutbound: "DELIVERY_OUTBOUND", + events.PortfolioEventTypeDepositCash: "DEPOSIT_CASH", + events.PortfolioEventTypeWithdrawCash: "WITHDRAW_CASH", + } +) + +func (ec *executionContext) unmarshalNPortfolioInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioInput(ctx context.Context, v any) (models.PortfolioInput, error) { + res, err := ec.unmarshalInputPortfolioInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNPortfolioPosition2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioPositionᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.PortfolioPosition) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNPortfolioPosition2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioPosition(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNPortfolioPosition2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioPosition(ctx context.Context, sel ast.SelectionSet, v *models.PortfolioPosition) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._PortfolioPosition(ctx, sel, v) +} + +func (ec *executionContext) marshalNSecurity2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx context.Context, sel ast.SelectionSet, v persistence.Security) graphql.Marshaler { + return ec._Security(ctx, sel, &v) +} + +func (ec *executionContext) marshalNSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurityᚄ(ctx context.Context, sel ast.SelectionSet, v []*persistence.Security) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx context.Context, sel ast.SelectionSet, v *persistence.Security) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Security(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNSecurityInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐSecurityInput(ctx context.Context, v any) (models.SecurityInput, error) { + res, err := ec.unmarshalInputSecurityInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNString2string(ctx context.Context, v any) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNString2ᚕstringᚄ(ctx context.Context, v any) ([]string, error) { + var vSlice []any + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v any) (time.Time, error) { + res, err := graphql.UnmarshalTime(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler { + res := graphql.MarshalTime(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalNTransaction2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransaction(ctx context.Context, sel ast.SelectionSet, v persistence.Transaction) graphql.Marshaler { + return ec._Transaction(ctx, sel, &v) +} + +func (ec *executionContext) marshalNTransaction2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransactionᚄ(ctx context.Context, sel ast.SelectionSet, v []*persistence.Transaction) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNTransaction2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransaction(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNTransaction2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐTransaction(ctx context.Context, sel ast.SelectionSet, v *persistence.Transaction) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Transaction(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNTransactionInput2githubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐTransactionInput(ctx context.Context, v any) (models.TransactionInput, error) { + res, err := ec.unmarshalInputTransactionInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { + return ec.___Directive(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirectiveᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Directive) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalN__DirectiveLocation2string(ctx context.Context, v any) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalN__DirectiveLocation2ᚕstringᚄ(ctx context.Context, v any) ([]string, error) { + var vSlice []any + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalN__DirectiveLocation2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalN__DirectiveLocation2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__DirectiveLocation2string(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__EnumValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValue(ctx context.Context, sel ast.SelectionSet, v introspection.EnumValue) graphql.Marshaler { + return ec.___EnumValue(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Field2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐField(ctx context.Context, sel ast.SelectionSet, v introspection.Field) graphql.Marshaler { + return ec.___Field(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx context.Context, sel ast.SelectionSet, v introspection.InputValue) graphql.Marshaler { + return ec.___InputValue(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.InputValue) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v introspection.Type) graphql.Marshaler { + return ec.___Type(ctx, sel, &v) +} + +func (ec *executionContext) marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Type) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec.___Type(ctx, sel, v) +} + +func (ec *executionContext) unmarshalN__TypeKind2string(ctx context.Context, v any) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) marshalOAccount2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐAccount(ctx context.Context, sel ast.SelectionSet, v *persistence.Account) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Account(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v any) (bool, error) { + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { + res := graphql.MarshalBoolean(v) + return res +} + +func (ec *executionContext) unmarshalOBoolean2ᚖbool(ctx context.Context, v any) (*bool, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalBoolean(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast.SelectionSet, v *bool) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalBoolean(*v) + return res +} + +func (ec *executionContext) marshalOCurrency2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋcurrencyᚐCurrency(ctx context.Context, sel ast.SelectionSet, v *currency.Currency) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Currency(ctx, sel, v) +} + +func (ec *executionContext) marshalOListedSecurity2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurityᚄ(ctx context.Context, sel ast.SelectionSet, v []*persistence.ListedSecurity) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNListedSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalOListedSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐListedSecurity(ctx context.Context, sel ast.SelectionSet, v *persistence.ListedSecurity) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._ListedSecurity(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOListedSecurityInput2ᚕᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐListedSecurityInputᚄ(ctx context.Context, v any) ([]*models.ListedSecurityInput, error) { + if v == nil { + return nil, nil + } + var vSlice []any + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*models.ListedSecurityInput, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNListedSecurityInput2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐListedSecurityInput(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalOPortfolio2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐPortfolio(ctx context.Context, sel ast.SelectionSet, v *persistence.Portfolio) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Portfolio(ctx, sel, v) +} + +func (ec *executionContext) marshalOPortfolioSnapshot2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋmodelsᚐPortfolioSnapshot(ctx context.Context, sel ast.SelectionSet, v *models.PortfolioSnapshot) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._PortfolioSnapshot(ctx, sel, v) +} + +func (ec *executionContext) marshalOSecurity2ᚖgithubᚗcomᚋoxistoᚋmoneyᚑgopherᚋpersistenceᚐSecurity(ctx context.Context, sel ast.SelectionSet, v *persistence.Security) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Security(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v any) ([]string, error) { + if v == nil { + return nil, nil + } + var vSlice []any + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalOString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v any) (*string, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalString(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(*v) + return res +} + +func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v any) (*time.Time, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalTime(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalTime(*v) + return res +} + +func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__EnumValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Field2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐFieldᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Field) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Field2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐField(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.InputValue) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__InputValue2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐInputValue(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx context.Context, sel ast.SelectionSet, v *introspection.Schema) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec.___Schema(ctx, sel, v) +} + +func (ec *executionContext) marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Type) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalN__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec.___Type(ctx, sel, v) +} + +// endregion ***************************** type.gotpl ***************************** diff --git a/graph/resolver.go b/graph/resolver.go new file mode 100644 index 00000000..4e6b627d --- /dev/null +++ b/graph/resolver.go @@ -0,0 +1,36 @@ +package graph + +import ( + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/securities/quote" +) + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type Resolver struct { + DB *persistence.DB + QuoteUpdater quote.QuoteUpdater +} + +func withTx[T any](r *Resolver, f func(qtx *persistence.Queries) (*T, error)) (res *T, err error) { + tx, err := r.DB.Begin() + if err != nil { + return nil, err + } + defer tx.Rollback() + + qtx := r.DB.WithTx(tx) + res, err = f(qtx) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/graph/schema.graphqls b/graph/schema.graphqls new file mode 100644 index 00000000..b2237336 --- /dev/null +++ b/graph/schema.graphqls @@ -0,0 +1,243 @@ +directive @goModel( + model: String + models: [String!] +) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION + +directive @goEnum(value: String) on ENUM_VALUE + +scalar Time + +type Currency { + amount: Int! + symbol: String! +} + +type Security { + id: String! + displayName: String! + quoteProvider: String + listedAs: [ListedSecurity!] +} + +type ListedSecurity { + ticker: String! + currency: String! + security: Security! + latestQuote: Currency + latestQuoteTimestamp: Time +} + +type Portfolio { + id: String! + displayName: String! + accounts: [Account!]! + snapshot(when: Time): PortfolioSnapshot + events: [PortfolioEvent!]! +} + +enum PortfolioEventType + @goModel( + model: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventType" + ) { + BUY + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeBuy" + ) + SELL + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeSell" + ) + DIVIDEND + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeDividend" + ) + DELIVERY_INBOUND + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeDeliveryInbound" + ) + DELIVERY_OUTBOUND + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeDeliveryOutbound" + ) + DEPOSIT_CASH + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeDepositCash" + ) + WITHDRAW_CASH + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/events.PortfolioEventTypeWithdrawCash" + ) +} + +enum AccountType + @goModel( + model: "github.com/oxisto/money-gopher/portfolio/accounts.AccountType" + ) { + BROKERAGE + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/accounts.AccountTypeBrokerage" + ) + BANK + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/accounts.AccountTypeBank" + ) + LOAN + @goEnum( + value: "github.com/oxisto/money-gopher/portfolio/accounts.AccountTypeLoan" + ) +} + +type PortfolioEvent { + time: Time! + type: PortfolioEventType! + security: Security +} + +type Transaction { + id: String! + time: Time! + sourceAccount: Account! + destinationAccount: Account! + security: Security! + amount: Float! + price: Currency! + fees: Currency! + type: PortfolioEventType! +} + +type PortfolioSnapshot { + time: Time! + positions: [PortfolioPosition!]! + firstTransactionTime: Time! + totalPurchaseValue: Currency! + totalMarketValue: Currency! + totalProfitOrLoss: Currency! + totalGains: Float! + totalPortfolioValue: Currency + cash: Currency! +} + +type PortfolioPosition { + security: Security! + amount: Float! + + """ + PurchaseValue was the market value of this position when it was bought (net; + exclusive of any fees). + """ + purchaseValue: Currency! + + """ + PurchasePrice was the market price of this position when it was bought (net; + exclusive of any fees). + """ + purchasePrice: Currency! + + """ + MarketValue is the current market value of this position, as retrieved from + the securities service. + """ + marketValue: Currency! + + """ + MarketPrice is the current market price of this position, as retrieved from + the securities service. + """ + marketPrice: Currency! + + """ + TotalFees is the total amount of fees accumulating in this position through + various transactions. + """ + totalFees: Currency! + + """ + ProfitOrLoss contains the absolute amount of profit or loss in this position. + """ + profitOrLoss: Currency! + + """ + Gains contains the relative amount of profit or loss in this position. + """ + gains: Float! +} + +type Account { + id: String! + displayName: String! + type: AccountType! + referenceAccount: Account +} + +input SecurityInput { + id: String! + displayName: String! + listedAs: [ListedSecurityInput!] +} + +input PortfolioInput { + id: String! + displayName: String! + accountIds: [String!]! +} + +input AccountInput { + id: String! + displayName: String! + type: AccountType! +} + +input ListedSecurityInput { + ticker: String! + currency: String! +} + +input TransactionInput { + time: Time! + sourceAccountID: String! + destinationAccountID: String! + securityID: String! + amount: Float! + price: CurrencyInput! + fees: CurrencyInput! + taxes: CurrencyInput! + type: PortfolioEventType! +} + +input CurrencyInput { + amount: Int! + symbol: String! +} + +type Mutation { + createSecurity(input: SecurityInput!): Security! + updateSecurity(id: ID!, input: SecurityInput!): Security! + + createPortfolio(input: PortfolioInput!): Portfolio! + updatePortfolio(id: ID!, input: PortfolioInput!): Portfolio! + + createAccount(input: AccountInput!): Account! + deleteAccount(id: String!): Account! + + createTransaction(input: TransactionInput!): Transaction! + updateTransaction(id: String!, input: TransactionInput!): Transaction! + + """ + Triggers a quote update for the given security IDs. If no security IDs are + provided, all securities will be updated. + """ + triggerQuoteUpdate(securityIDs: [String!]): [ListedSecurity]! +} + +type Query { + security(id: String!): Security + securities: [Security!]! + + portfolio(id: String!): Portfolio + portfolios: [Portfolio!]! + + account(id: String!): Account + accounts: [Account!]! + + transactions(accountID: String!): [Transaction!]! +} diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go new file mode 100644 index 00000000..536c057b --- /dev/null +++ b/graph/schema.resolvers.go @@ -0,0 +1,304 @@ +package graph + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.61 + +import ( + "context" + "fmt" + "slices" + "time" + + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/finance" + "github.com/oxisto/money-gopher/models" + "github.com/oxisto/money-gopher/persistence" +) + +// ReferenceAccount is the resolver for the referenceAccount field. +func (r *accountResolver) ReferenceAccount(ctx context.Context, obj *persistence.Account) (*persistence.Account, error) { + panic(fmt.Errorf("not implemented: ReferenceAccount - referenceAccount")) +} + +// Security is the resolver for the security field. +func (r *listedSecurityResolver) Security(ctx context.Context, obj *persistence.ListedSecurity) (*persistence.Security, error) { + panic(fmt.Errorf("not implemented: Security - security")) +} + +// CreateSecurity is the resolver for the createSecurity field. +func (r *mutationResolver) CreateSecurity(ctx context.Context, input models.SecurityInput) (*persistence.Security, error) { + return withTx(r.Resolver, func(qtx *persistence.Queries) (*persistence.Security, error) { + sec, err := qtx.CreateSecurity(ctx, persistence.CreateSecurityParams{ + ID: input.ID, + DisplayName: input.DisplayName, + }) + if err != nil { + return nil, err + } + + for _, listed := range input.ListedAs { + _, err = qtx.UpsertListedSecurity(ctx, persistence.UpsertListedSecurityParams{ + SecurityID: sec.ID, + Ticker: listed.Ticker, + Currency: listed.Currency, + }) + if err != nil { + return nil, err + } + } + + return sec, nil + }) +} + +// UpdateSecurity is the resolver for the updateSecurity field. +func (r *mutationResolver) UpdateSecurity(ctx context.Context, id string, input models.SecurityInput) (*persistence.Security, error) { + return withTx(r.Resolver, func(qtx *persistence.Queries) (*persistence.Security, error) { + sec, err := qtx.UpdateSecurity(ctx, persistence.UpdateSecurityParams{ + ID: id, + DisplayName: input.DisplayName, + }) + if err != nil { + return nil, err + } + + // Retrieve old listed securities + oldListed, err := qtx.ListListedSecuritiesBySecurityID(ctx, id) + if err != nil { + return nil, err + } + + // Upsert the new listed securities + for _, listed := range input.ListedAs { + _, err = qtx.UpsertListedSecurity(ctx, persistence.UpsertListedSecurityParams{ + SecurityID: sec.ID, + Ticker: listed.Ticker, + Currency: listed.Currency, + }) + if err != nil { + return nil, err + } + + // Remove the listed security from the old list + oldListed = slices.DeleteFunc(oldListed, func(ls *persistence.ListedSecurity) bool { + return ls.Ticker == listed.Ticker + }) + } + + // Remove the old listed securities + for _, old := range oldListed { + _, err = qtx.DeleteListedSecurity(ctx, persistence.DeleteListedSecurityParams{ + SecurityID: sec.ID, + Ticker: old.Ticker, + }) + if err != nil { + return nil, err + } + } + + return sec, nil + }) +} + +// CreatePortfolio is the resolver for the createPortfolio field. +func (r *mutationResolver) CreatePortfolio(ctx context.Context, input models.PortfolioInput) (*persistence.Portfolio, error) { + return withTx(r.Resolver, func(qtx *persistence.Queries) (*persistence.Portfolio, error) { + sec, err := qtx.CreatePortfolio(ctx, persistence.CreatePortfolioParams{ + ID: input.ID, + DisplayName: input.DisplayName, + }) + if err != nil { + return nil, err + } + + for _, accountID := range input.AccountIds { + err = qtx.AddAccountToPortfolio(ctx, persistence.AddAccountToPortfolioParams{ + PortfolioID: input.ID, + AccountID: accountID, + }) + if err != nil { + return nil, err + } + } + + return sec, nil + }) +} + +// UpdatePortfolio is the resolver for the updatePortfolio field. +func (r *mutationResolver) UpdatePortfolio(ctx context.Context, id string, input models.PortfolioInput) (*persistence.Portfolio, error) { + return withTx(r.Resolver, func(qtx *persistence.Queries) (*persistence.Portfolio, error) { + sec, err := qtx.UpdatePortfolio(ctx, persistence.UpdatePortfolioParams{ + ID: input.ID, + DisplayName: input.DisplayName, + }) + if err != nil { + return nil, err + } + + for _, accountID := range input.AccountIds { + err = qtx.AddAccountToPortfolio(ctx, persistence.AddAccountToPortfolioParams{ + PortfolioID: input.ID, + AccountID: accountID, + }) + if err != nil { + return nil, err + } + } + + return sec, nil + }) +} + +// CreateAccount is the resolver for the createAccount field. +func (r *mutationResolver) CreateAccount(ctx context.Context, input models.AccountInput) (*persistence.Account, error) { + return r.DB.CreateAccount(ctx, persistence.CreateAccountParams{ + ID: input.ID, + DisplayName: input.DisplayName, + Type: input.Type, + }) +} + +// DeleteAccount is the resolver for the deleteAccount field. +func (r *mutationResolver) DeleteAccount(ctx context.Context, id string) (*persistence.Account, error) { + return r.DB.DeleteAccount(ctx, id) +} + +// CreateTransaction is the resolver for the createTransaction field. +func (r *mutationResolver) CreateTransaction(ctx context.Context, input models.TransactionInput) (*persistence.Transaction, error) { + return r.DB.CreateTransaction(ctx, persistence.CreateTransactionParams{ + ID: "newID", + Time: input.Time, + SourceAccountID: &input.SourceAccountID, + DestinationAccountID: &input.DestinationAccountID, + SecurityID: &input.SecurityID, + Type: input.Type, + Amount: input.Amount, + Price: ¤cy.Currency{Amount: int32(input.Price.Amount), Symbol: input.Price.Symbol}, + Fees: ¤cy.Currency{Amount: int32(input.Fees.Amount), Symbol: input.Fees.Symbol}, + Taxes: ¤cy.Currency{Amount: int32(input.Taxes.Amount), Symbol: input.Taxes.Symbol}, + }) +} + +// UpdateTransaction is the resolver for the updateTransaction field. +func (r *mutationResolver) UpdateTransaction(ctx context.Context, id string, input models.TransactionInput) (*persistence.Transaction, error) { + panic(fmt.Errorf("not implemented: UpdateTransaction - updateTransaction")) +} + +// TriggerQuoteUpdate is the resolver for the triggerQuoteUpdate field. +func (r *mutationResolver) TriggerQuoteUpdate(ctx context.Context, securityIDs []string) (updated []*persistence.ListedSecurity, err error) { + updated, err = r.QuoteUpdater.UpdateQuotes(ctx, securityIDs) + if err != nil { + return nil, err + } + + return +} + +// Accounts is the resolver for the accounts field. +func (r *portfolioResolver) Accounts(ctx context.Context, obj *persistence.Portfolio) ([]*persistence.Account, error) { + return r.DB.ListAccountsByPortfolioID(ctx, obj.ID) +} + +// Snapshot is the resolver for the snapshot field. +func (r *portfolioResolver) Snapshot(ctx context.Context, obj *persistence.Portfolio, when *time.Time) (snap *models.PortfolioSnapshot, err error) { + var t time.Time + + if when == nil { + t = time.Now() + } else { + t = *when + } + + return finance.BuildSnapshot(ctx, t, obj.ID, r.DB) +} + +// Events is the resolver for the events field. +func (r *portfolioResolver) Events(ctx context.Context, obj *persistence.Portfolio) ([]*models.PortfolioEvent, error) { + panic(fmt.Errorf("not implemented: Events - events")) +} + +// Security is the resolver for the security field. +func (r *queryResolver) Security(ctx context.Context, id string) (*persistence.Security, error) { + return r.DB.GetSecurity(ctx, id) +} + +// Securities is the resolver for the securities field. +func (r *queryResolver) Securities(ctx context.Context) ([]*persistence.Security, error) { + return r.DB.ListSecurities(ctx) +} + +// Portfolio is the resolver for the portfolio field. +func (r *queryResolver) Portfolio(ctx context.Context, id string) (*persistence.Portfolio, error) { + return r.DB.GetPortfolio(ctx, id) +} + +// Portfolios is the resolver for the portfolios field. +func (r *queryResolver) Portfolios(ctx context.Context) ([]*persistence.Portfolio, error) { + return r.DB.ListPortfolios(ctx) +} + +// Account is the resolver for the account field. +func (r *queryResolver) Account(ctx context.Context, id string) (*persistence.Account, error) { + return r.DB.GetAccount(ctx, id) +} + +// Accounts is the resolver for the accounts field. +func (r *queryResolver) Accounts(ctx context.Context) ([]*persistence.Account, error) { + return r.DB.ListAccounts(ctx) +} + +// Transactions is the resolver for the transactions field. +func (r *queryResolver) Transactions(ctx context.Context, accountID string) ([]*persistence.Transaction, error) { + return r.DB.ListTransactionsByAccountID(ctx, &accountID) +} + +// ListedAs is the resolver for the listedAs field. +func (r *securityResolver) ListedAs(ctx context.Context, obj *persistence.Security) ([]*persistence.ListedSecurity, error) { + return obj.ListedAs(ctx, r.DB) +} + +// SourceAccount is the resolver for the sourceAccount field. +func (r *transactionResolver) SourceAccount(ctx context.Context, obj *persistence.Transaction) (*persistence.Account, error) { + panic(fmt.Errorf("not implemented: SourceAccount - sourceAccount")) +} + +// DestinationAccount is the resolver for the destinationAccount field. +func (r *transactionResolver) DestinationAccount(ctx context.Context, obj *persistence.Transaction) (*persistence.Account, error) { + panic(fmt.Errorf("not implemented: DestinationAccount - destinationAccount")) +} + +// Security is the resolver for the security field. +func (r *transactionResolver) Security(ctx context.Context, obj *persistence.Transaction) (*persistence.Security, error) { + panic(fmt.Errorf("not implemented: Security - security")) +} + +// Account returns AccountResolver implementation. +func (r *Resolver) Account() AccountResolver { return &accountResolver{r} } + +// ListedSecurity returns ListedSecurityResolver implementation. +func (r *Resolver) ListedSecurity() ListedSecurityResolver { return &listedSecurityResolver{r} } + +// Mutation returns MutationResolver implementation. +func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } + +// Portfolio returns PortfolioResolver implementation. +func (r *Resolver) Portfolio() PortfolioResolver { return &portfolioResolver{r} } + +// Query returns QueryResolver implementation. +func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } + +// Security returns SecurityResolver implementation. +func (r *Resolver) Security() SecurityResolver { return &securityResolver{r} } + +// Transaction returns TransactionResolver implementation. +func (r *Resolver) Transaction() TransactionResolver { return &transactionResolver{r} } + +type accountResolver struct{ *Resolver } +type listedSecurityResolver struct{ *Resolver } +type mutationResolver struct{ *Resolver } +type portfolioResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } +type securityResolver struct{ *Resolver } +type transactionResolver struct{ *Resolver } diff --git a/graph/schema.resolvers_test.go b/graph/schema.resolvers_test.go new file mode 100644 index 00000000..076f8ac3 --- /dev/null +++ b/graph/schema.resolvers_test.go @@ -0,0 +1,280 @@ +package graph + +import ( + "context" + "reflect" + "testing" + + "github.com/oxisto/assert" + "github.com/oxisto/money-gopher/internal/testdata" + "github.com/oxisto/money-gopher/internal/testing/persistencetest" + "github.com/oxisto/money-gopher/models" + "github.com/oxisto/money-gopher/persistence" +) + +func Test_mutationResolver_CreateSecurity(t *testing.T) { + type fields struct { + Resolver *Resolver + } + type args struct { + ctx context.Context + input models.SecurityInput + } + tests := []struct { + name string + fields fields + args args + want *persistence.Security + wantDB assert.Want[*persistence.DB] + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + Resolver: &Resolver{ + DB: persistencetest.NewTestDB(t), + }, + }, + args: args{ + ctx: context.TODO(), + input: models.SecurityInput{ + ID: "DE1234567890", + DisplayName: "My Security", + ListedAs: []*models.ListedSecurityInput{ + { + Ticker: "TICK", + Currency: "USD", + }, + }, + }, + }, + want: &persistence.Security{ + ID: "DE1234567890", + DisplayName: "My Security", + }, + wantDB: func(t *testing.T, db *persistence.DB) bool { + _, err := db.Queries.GetSecurity(context.Background(), "DE1234567890") + assert.NoError(t, err) + + ls, err := db.Queries.ListListedSecuritiesBySecurityID(context.Background(), "DE1234567890") + return len(ls) == 1 && assert.NoError(t, err) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &mutationResolver{ + Resolver: tt.fields.Resolver, + } + got, err := r.CreateSecurity(tt.args.ctx, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("mutationResolver.CreateSecurity() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equals(t, tt.want, got) + tt.wantDB(t, tt.fields.Resolver.DB) + }) + } +} + +func Test_queryResolver_Accounts(t *testing.T) { + type fields struct { + Resolver *Resolver + } + type args struct { + ctx context.Context + } + tests := []struct { + name string + fields fields + args args + want []*persistence.Account + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + Resolver: &Resolver{ + DB: persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + }), + }, + }, + args: args{ + ctx: context.TODO(), + }, + want: []*persistence.Account{ + testdata.TestBankAccount, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &queryResolver{ + Resolver: tt.fields.Resolver, + } + got, err := r.Accounts(tt.args.ctx) + if (err != nil) != tt.wantErr { + t.Errorf("queryResolver.Accounts() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("queryResolver.Accounts() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_queryResolver_Transactions(t *testing.T) { + type fields struct { + Resolver *Resolver + } + type args struct { + ctx context.Context + accountID string + } + tests := []struct { + name string + fields fields + args args + want []*persistence.Transaction + wantErr bool + }{ + { + name: "list brokerage transactions", + fields: fields{ + Resolver: &Resolver{ + DB: persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateBuyTransactionParams) + assert.NoError(t, err) + }), + }, + }, + args: args{ + ctx: context.TODO(), + accountID: testdata.TestBrokerageAccount.ID, + }, + want: []*persistence.Transaction{ + testdata.TestBuyTransaction, + }, + }, + { + name: "list bank transactions", + fields: fields{ + Resolver: &Resolver{ + DB: persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateBuyTransactionParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateDepositTransactionParams) + assert.NoError(t, err) + }), + }, + }, + args: args{ + ctx: context.TODO(), + accountID: testdata.TestBankAccount.ID, + }, + want: []*persistence.Transaction{ + testdata.TestBuyTransaction, + testdata.TestDepositTransaction, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &queryResolver{ + Resolver: tt.fields.Resolver, + } + got, err := r.Transactions(tt.args.ctx, tt.args.accountID) + if (err != nil) != tt.wantErr { + t.Errorf("queryResolver.Transactions() error = %v, wantErr %v", err, tt.wantErr) + return + } + + assert.Equals(t, tt.want, got) + }) + } +} + +func Test_mutationResolver_CreatePortfolio(t *testing.T) { + type fields struct { + Resolver *Resolver + } + type args struct { + ctx context.Context + input models.PortfolioInput + } + tests := []struct { + name string + fields fields + args args + want *persistence.Portfolio + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + Resolver: &Resolver{ + DB: persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateAccount(context.Background(), testdata.TestCreateBankAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateAccount(context.Background(), testdata.TestCreateBrokerageAccountParams) + assert.NoError(t, err) + + _, err = db.Queries.CreateTransaction(context.Background(), testdata.TestCreateBuyTransactionParams) + assert.NoError(t, err) + }), + }, + }, + args: args{ + ctx: context.TODO(), + input: models.PortfolioInput{ + ID: "mybank/myportfolio", + DisplayName: "My Portfolio", + AccountIds: []string{ + testdata.TestCreateBankAccountParams.ID, + testdata.TestCreateBrokerageAccountParams.ID, + }, + }, + }, + want: &persistence.Portfolio{ + ID: "mybank/myportfolio", + DisplayName: "My Portfolio", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &mutationResolver{ + Resolver: tt.fields.Resolver, + } + got, err := r.CreatePortfolio(tt.args.ctx, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("mutationResolver.CreatePortfolio() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("mutationResolver.CreatePortfolio() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/import/csv/csv_importer.go b/import/csv/csv_importer.go index da128b77..d8ff4046 100644 --- a/import/csv/csv_importer.go +++ b/import/csv/csv_importer.go @@ -39,12 +39,12 @@ import ( "strings" "time" - moneygopher "github.com/oxisto/money-gopher" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/events" + "github.com/oxisto/money-gopher/securities/quote" "github.com/lmittmann/tint" - "github.com/oxisto/money-gopher/service/securities" - "google.golang.org/protobuf/types/known/timestamppb" ) var ( @@ -59,7 +59,15 @@ var ( // Import imports CSV records from a [io.Reader] containing portfolio // transactions. -func Import(r io.Reader, pname string) (txs []*portfoliov1.PortfolioEvent, secs []*portfoliov1.Security) { +func Import( + r io.Reader, + bankAccountID string, + brokerageAccountID string, +) ( + txs []*persistence.Transaction, + secs []*persistence.Security, + lss []*persistence.ListedSecurity, +) { cr := csv.NewReader(r) cr.Comma = ';' @@ -68,7 +76,7 @@ func Import(r io.Reader, pname string) (txs []*portfoliov1.PortfolioEvent, secs // Read until EOF for { - tx, sec, err := readLine(cr, pname) + tx, sec, ls, err := readLine(cr, bankAccountID, brokerageAccountID) if errors.Is(err, io.EOF) { break } else if err != nil { @@ -79,122 +87,135 @@ func Import(r io.Reader, pname string) (txs []*portfoliov1.PortfolioEvent, secs txs = append(txs, tx) secs = append(secs, sec) + lss = append(lss, ls...) } - // Compact securities - secs = slices.CompactFunc(secs, func(a *portfoliov1.Security, b *portfoliov1.Security) bool { - return a.Id == b.Id + // Make (listed) securities unique + secs = slices.CompactFunc(secs, func(a *persistence.Security, b *persistence.Security) bool { + return a.ID == b.ID + }) + lss = slices.CompactFunc(lss, func(a *persistence.ListedSecurity, b *persistence.ListedSecurity) bool { + return a.SecurityID == b.SecurityID && a.Ticker == b.Ticker }) return } -func readLine(cr *csv.Reader, pname string) (tx *portfoliov1.PortfolioEvent, sec *portfoliov1.Security, err error) { +func readLine( + cr *csv.Reader, + bankAccountID string, + brokerageAccountID string, +) ( + tx *persistence.Transaction, + sec *persistence.Security, + ls []*persistence.ListedSecurity, + err error) { var ( record []string - value *portfoliov1.Currency + value *currency.Currency ) record, err = cr.Read() if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrReadingCSV, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrReadingCSV, err) } - tx = new(portfoliov1.PortfolioEvent) + tx = new(persistence.Transaction) tx.Time, err = txTime(record[0]) if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrParsingTime, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrParsingTime, err) } tx.Type = txType(record[1]) - if tx.Type == portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_UNSPECIFIED { - return nil, nil, ErrParsingType + if tx.Type == events.PortfolioEventTypeUnknown { + return nil, nil, nil, ErrParsingType } value, err = parseFloatCurrency(record[2]) if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrParsingValue, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrParsingValue, err) } tx.Fees, err = parseFloatCurrency(record[7]) if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrParsingFees, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrParsingFees, err) } tx.Taxes, err = parseFloatCurrency(record[8]) if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrParsingTaxes, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrParsingTaxes, err) } tx.Amount, err = parseFloat64(record[9]) if err != nil { - return nil, nil, fmt.Errorf("%w: %w", ErrParsingAmount, err) + return nil, nil, nil, fmt.Errorf("%w: %w", ErrParsingAmount, err) } // Calculate the price - if tx.Type == portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY || - tx.Type == portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND { - tx.Price = portfoliov1.Divide(portfoliov1.Minus(value, tx.Fees), tx.Amount) - } else if tx.Type == portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL || - tx.Type == portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND { - tx.Price = portfoliov1.Times(portfoliov1.Divide(portfoliov1.Minus(portfoliov1.Minus(value, tx.Fees), tx.Taxes), tx.Amount), -1) - } - - sec = new(portfoliov1.Security) - sec.Id = record[10] + if tx.Type == events.PortfolioEventTypeBuy || + tx.Type == events.PortfolioEventTypeDeliveryInbound { + tx.Price = currency.Divide(currency.Minus(value, tx.Fees), tx.Amount) + tx.SourceAccountID = &bankAccountID + tx.DestinationAccountID = &brokerageAccountID + } else if tx.Type == events.PortfolioEventTypeSell || + tx.Type == events.PortfolioEventTypeDeliveryOutbound { + tx.Price = currency.Times(currency.Divide(currency.Minus(currency.Minus(value, tx.Fees), tx.Taxes), tx.Amount), -1) + tx.SourceAccountID = &brokerageAccountID + tx.DestinationAccountID = &bankAccountID + } + + sec = new(persistence.Security) + sec.ID = record[10] sec.DisplayName = record[13] - sec.ListedOn = []*portfoliov1.ListedSecurity{ - { - SecurityId: sec.Id, - Ticker: record[12], - Currency: lsCurrency(record[3], record[5]), - }, - } + + ls = append(ls, &persistence.ListedSecurity{ + SecurityID: sec.ID, + Ticker: record[12], + Currency: lsCurrency(record[3], record[5]), + }) // Default to YF, but only if we have a ticker symbol, otherwise, let's try ING - if len(sec.ListedOn) >= 0 && len(sec.ListedOn[0].Ticker) > 0 { - sec.QuoteProvider = moneygopher.Ref(securities.QuoteProviderYF) + var qp string + if len(ls) >= 0 && len(ls[0].Ticker) > 0 { + qp = quote.QuoteProviderYF } else { - sec.QuoteProvider = moneygopher.Ref(securities.QuoteProviderING) + qp = quote.QuoteProviderING } + sec.QuoteProvider = &qp - tx.PortfolioId = pname - tx.SecurityId = sec.Id + tx.SecurityID = &sec.ID tx.MakeUniqueID() return } -func txType(typ string) portfoliov1.PortfolioEventType { +func txType(typ string) events.PortfolioEventType { switch typ { case "Buy": - return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY + return events.PortfolioEventTypeBuy case "Sell": - return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL + return events.PortfolioEventTypeSell case "Delivery (Inbound)": - return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND + return events.PortfolioEventTypeDeliveryInbound case "Delivery (Outbound)": - return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND + return events.PortfolioEventTypeDeliveryOutbound default: - return portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_UNSPECIFIED + return events.PortfolioEventTypeUnknown } } -func txTime(s string) (ts *timestamppb.Timestamp, err error) { - var ( - t time.Time - ) +func txTime(s string) (t time.Time, err error) { // First try without seconds t, err = time.ParseInLocation("2006-01-02T15:04", s, time.Local) if err != nil { // Then with seconds t, err = time.ParseInLocation("2006-01-02T15:04:05", s, time.Local) if err != nil { - return nil, err + return time.Time{}, err } } - return timestamppb.New(t), nil + return t, nil } func parseFloat64(s string) (f float64, err error) { @@ -211,17 +232,17 @@ func parseFloat64(s string) (f float64, err error) { return } -func parseFloatCurrency(s string) (c *portfoliov1.Currency, err error) { +func parseFloatCurrency(s string) (c *currency.Currency, err error) { // Get rid of all , and . s = strings.ReplaceAll(s, ".", "") s = strings.ReplaceAll(s, ",", "") i, err := strconv.ParseInt(s, 10, 32) if err != nil { - return portfoliov1.Zero(), err + return currency.Zero(), err } - return portfoliov1.Value(int32(i)), nil + return currency.Value(int32(i)), nil } func lsCurrency(txCurrency string, tickerCurrency string) string { diff --git a/import/csv/csv_importer_test.go b/import/csv/csv_importer_test.go index 47805924..26008da0 100644 --- a/import/csv/csv_importer_test.go +++ b/import/csv/csv_importer_test.go @@ -24,24 +24,27 @@ import ( "time" moneygopher "github.com/oxisto/money-gopher" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/internal/testdata" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/events" + "github.com/oxisto/money-gopher/securities/quote" "github.com/oxisto/assert" - "github.com/oxisto/money-gopher/service/securities" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" ) func TestImport(t *testing.T) { type args struct { - r io.Reader - pname string + r io.Reader + bankAccountID string + brokerageAccountID string } tests := []struct { name string args args - wantTxs assert.Want[[]*portfoliov1.PortfolioEvent] - wantSecs assert.Want[[]*portfoliov1.Security] + wantTxs assert.Want[[]*persistence.Transaction] + wantSecs assert.Want[[]*persistence.Security] + wantLss assert.Want[[]*persistence.ListedSecurity] }{ { name: "happy path", @@ -50,13 +53,18 @@ func TestImport(t *testing.T) { 2021-06-05T00:00;Buy;2.151,85;EUR;;;;10,25;0,00;20;US0378331005;865985;APC.F;Apple Inc.; 2021-06-05T00:00;Sell;-2.151,85;EUR;;;;10,25;0,00;20;US0378331005;865985;APC.F;Apple Inc.; 2021-06-18T00:00;Delivery (Inbound);912,66;EUR;;;;7,16;0,00;5;US09075V1026;A2PSR2;22UA.F;BioNTech SE;`)), + bankAccountID: testdata.TestBankAccount.ID, + brokerageAccountID: testdata.TestBrokerageAccount.ID, }, - wantTxs: func(t *testing.T, txs []*portfoliov1.PortfolioEvent) bool { + wantTxs: func(t *testing.T, txs []*persistence.Transaction) bool { return assert.Equals(t, 3, len(txs)) }, - wantSecs: func(t *testing.T, secs []*portfoliov1.Security) bool { + wantSecs: func(t *testing.T, secs []*persistence.Security) bool { return assert.Equals(t, 2, len(secs)) }, + wantLss: func(t *testing.T, lss []*persistence.ListedSecurity) bool { + return assert.Equals(t, 2, len(lss)) + }, }, { name: "error", @@ -64,33 +72,39 @@ func TestImport(t *testing.T) { r: bytes.NewReader([]byte(`Date;Type;Value;Transaction Currency;Gross Amount;Currency Gross Amount;Exchange Rate;Fees;Taxes;Shares;ISIN;WKN;Ticker Symbol;Security Name;Note this;will;be;an;error`)), }, - wantTxs: func(t *testing.T, txs []*portfoliov1.PortfolioEvent) bool { + wantTxs: func(t *testing.T, txs []*persistence.Transaction) bool { return assert.Equals(t, 0, len(txs)) }, - wantSecs: func(t *testing.T, secs []*portfoliov1.Security) bool { + wantSecs: func(t *testing.T, secs []*persistence.Security) bool { return assert.Equals(t, 0, len(secs)) }, + wantLss: func(t *testing.T, lss []*persistence.ListedSecurity) bool { + return assert.Equals(t, 0, len(lss)) + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotTxs, gotSecs := Import(tt.args.r, tt.args.pname) + gotTxs, gotSecs, gotLss := Import(tt.args.r, tt.args.bankAccountID, tt.args.brokerageAccountID) tt.wantTxs(t, gotTxs) tt.wantSecs(t, gotSecs) + tt.wantLss(t, gotLss) }) } } func Test_readLine(t *testing.T) { type args struct { - cr *csv.Reader - pname string + cr *csv.Reader + bankAccountID string + brokerageAccountID string } tests := []struct { name string args args - wantTx *portfoliov1.PortfolioEvent - wantSec *portfoliov1.Security + wantTx *persistence.Transaction + wantSec *persistence.Security + wantLs []*persistence.ListedSecurity wantErr assert.Want[error] }{ { @@ -101,27 +115,31 @@ func Test_readLine(t *testing.T) { cr.Comma = ';' return cr }(), + bankAccountID: testdata.TestBankAccount.ID, + brokerageAccountID: testdata.TestBrokerageAccount.ID, }, - wantTx: &portfoliov1.PortfolioEvent{ - Id: "9e7b470b7566beca", - SecurityId: "US0378331005", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local)), - Amount: 20, - Fees: portfoliov1.Value(1025), - Taxes: portfoliov1.Zero(), - Price: portfoliov1.Value(10708), - }, - wantSec: &portfoliov1.Security{ - Id: "US0378331005", + wantTx: &persistence.Transaction{ + ID: "670240c1f8373a3f", + SecurityID: moneygopher.Ref("US0378331005"), + Type: events.PortfolioEventTypeBuy, + SourceAccountID: &testdata.TestBankAccount.ID, + DestinationAccountID: &testdata.TestBrokerageAccount.ID, + Time: time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local), + Amount: 20, + Fees: currency.Value(1025), + Taxes: currency.Zero(), + Price: currency.Value(10708), + }, + wantSec: &persistence.Security{ + ID: "US0378331005", DisplayName: "Apple Inc.", - QuoteProvider: moneygopher.Ref(securities.QuoteProviderYF), - ListedOn: []*portfoliov1.ListedSecurity{ - { - SecurityId: "US0378331005", - Ticker: "APC.F", - Currency: "EUR", - }, + QuoteProvider: moneygopher.Ref(quote.QuoteProviderYF), + }, + wantLs: []*persistence.ListedSecurity{ + { + SecurityID: "US0378331005", + Ticker: "APC.F", + Currency: "EUR", }, }, }, @@ -133,27 +151,31 @@ func Test_readLine(t *testing.T) { cr.Comma = ';' return cr }(), + bankAccountID: testdata.TestBankAccount.ID, + brokerageAccountID: testdata.TestBrokerageAccount.ID, }, - wantTx: &portfoliov1.PortfolioEvent{ - Id: "1070dafc882785a0", - SecurityId: "US00827B1061", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - Time: timestamppb.New(time.Date(2022, 1, 1, 9, 0, 0, 0, time.Local)), - Amount: 20, - Price: portfoliov1.Value(6040), - Fees: portfoliov1.Zero(), - Taxes: portfoliov1.Zero(), - }, - wantSec: &portfoliov1.Security{ - Id: "US00827B1061", + wantTx: &persistence.Transaction{ + ID: "dbddb1c7b1ce1375", + SecurityID: moneygopher.Ref("US00827B1061"), + Type: events.PortfolioEventTypeBuy, + SourceAccountID: &testdata.TestBankAccount.ID, + DestinationAccountID: &testdata.TestBrokerageAccount.ID, + Time: time.Date(2022, 1, 1, 9, 0, 0, 0, time.Local), + Amount: 20, + Price: currency.Value(6040), + Fees: currency.Zero(), + Taxes: currency.Zero(), + }, + wantSec: &persistence.Security{ + ID: "US00827B1061", DisplayName: "Affirm Holdings Inc.", - QuoteProvider: moneygopher.Ref(securities.QuoteProviderYF), - ListedOn: []*portfoliov1.ListedSecurity{ - { - SecurityId: "US00827B1061", - Ticker: "AFRM", - Currency: "USD", - }, + QuoteProvider: moneygopher.Ref(quote.QuoteProviderYF), + }, + wantLs: []*persistence.ListedSecurity{ + { + SecurityID: "US00827B1061", + Ticker: "AFRM", + Currency: "USD", }, }, }, @@ -165,27 +187,31 @@ func Test_readLine(t *testing.T) { cr.Comma = ';' return cr }(), + bankAccountID: testdata.TestBankAccount.ID, + brokerageAccountID: testdata.TestBrokerageAccount.ID, }, - wantTx: &portfoliov1.PortfolioEvent{ - Id: "8bb43fed65b35685", - SecurityId: "DE0005557508", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, - Time: timestamppb.New(time.Date(2022, 1, 1, 8, 0, 6, 0, time.Local)), - Amount: 103, - Fees: portfoliov1.Zero(), - Taxes: portfoliov1.Value(1830), - Price: portfoliov1.Value(1552), - }, - wantSec: &portfoliov1.Security{ - Id: "DE0005557508", + wantTx: &persistence.Transaction{ + ID: "4201924709e1f078", + SecurityID: moneygopher.Ref("DE0005557508"), + SourceAccountID: &testdata.TestBrokerageAccount.ID, + DestinationAccountID: &testdata.TestBankAccount.ID, + Type: events.PortfolioEventTypeSell, + Time: time.Date(2022, 1, 1, 8, 0, 6, 0, time.Local), + Amount: 103, + Fees: currency.Zero(), + Taxes: currency.Value(1830), + Price: currency.Value(1552), + }, + wantSec: &persistence.Security{ + ID: "DE0005557508", DisplayName: "Deutsche Telekom AG", - QuoteProvider: moneygopher.Ref(securities.QuoteProviderYF), - ListedOn: []*portfoliov1.ListedSecurity{ - { - SecurityId: "DE0005557508", - Ticker: "DTE.F", - Currency: "EUR", - }, + QuoteProvider: moneygopher.Ref(quote.QuoteProviderYF), + }, + wantLs: []*persistence.ListedSecurity{ + { + SecurityID: "DE0005557508", + Ticker: "DTE.F", + Currency: "EUR", }, }, }, @@ -270,13 +296,14 @@ func Test_readLine(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotTx, gotSec, err := readLine(tt.args.cr, tt.args.pname) + gotTx, gotSec, gotLs, err := readLine(tt.args.cr, tt.args.bankAccountID, tt.args.brokerageAccountID) if err != nil { tt.wantErr(t, err) return } - assert.Equals(t, tt.wantTx, gotTx, protocmp.Transform()) - assert.Equals(t, tt.wantSec, gotSec, protocmp.Transform()) + assert.Equals(t, tt.wantTx, gotTx) + assert.Equals(t, tt.wantSec, gotSec) + assert.Equals(t, tt.wantLs, gotLs) }) } } diff --git a/internal/enum/valueof.go b/internal/enum/valueof.go new file mode 100644 index 00000000..d7a8bcaf --- /dev/null +++ b/internal/enum/valueof.go @@ -0,0 +1,50 @@ +package enum + +import ( + "encoding/json" + "flag" + "fmt" +) + +// Enum is an interface for enum types that support [flag.Value]. +type Enum interface { + flag.Value +} + +// ValueOf returns the index of the value v in the name/index slice. +func ValueOf(v string, name string, index []uint8) int { + for i := range len(index) - 1 { + if name[index[i]:index[i+1]] == v { + return i + 1 + } + } + + return -1 +} + +// Set sets the target enum to the value represented by v (using [ValueOf]). +func Set[T ~int](enum *T, v string, name string, index []uint8) error { + i := ValueOf(v, name, index) + if i == -1 { + return fmt.Errorf("unknown value: %s", v) + } else { + *enum = T(i) + return nil + } +} + +// MarshalJSON marshals the enum to JSON using the string representation. +func MarshalJSON[T fmt.Stringer](enum T) ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, enum.String())), nil +} + +// UnmarshalJSON unmarshals the enum from JSON. It expects a string +// representation. +func UnmarshalJSON[T Enum](enum T, data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + return enum.Set(s) +} diff --git a/internal/persistence.go b/internal/persistence.go index 9ac60b1c..02507667 100644 --- a/internal/persistence.go +++ b/internal/persistence.go @@ -27,7 +27,7 @@ func NewTestDB(t *testing.T, inits ...func(db *persistence.DB)) (db *persistence err error ) - db, _, err = persistence.OpenDB(persistence.Options{UseInMemory: true}) + db, err = persistence.OpenDB(persistence.Options{UseInMemory: true}) if err != nil { t.Fatalf("Could not create test DB: %v", err) } diff --git a/internal/testdata/securities.go b/internal/testdata/securities.go new file mode 100644 index 00000000..45380f27 --- /dev/null +++ b/internal/testdata/securities.go @@ -0,0 +1,141 @@ +package testdata + +import ( + "time" + + "github.com/google/uuid" + moneygopher "github.com/oxisto/money-gopher" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/internal/testing/quotetest" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" +) + +// TestSecurity is a test security. +var TestSecurity = &persistence.Security{ + ID: "DE1234567890", + DisplayName: "My Security", + QuoteProvider: moneygopher.Ref(quotetest.QuoteProviderStatic), +} + +// TestListedSecurity is a listed security for [TestSecurity] that has a ticker +// "TICK" and currency "USD". +var TestListedSecurity = &persistence.ListedSecurity{ + SecurityID: TestSecurity.ID, + Ticker: "TICK", + Currency: "USD", +} + +// TestCreateSecurityParams is a test security creation parameter. +var TestCreateSecurityParams = persistence.CreateSecurityParams{ + ID: TestSecurity.ID, + DisplayName: TestSecurity.DisplayName, + QuoteProvider: TestSecurity.QuoteProvider, +} + +// TestUpsertListedSecurityParams is a test listed security upsert parameter. +var TestUpsertListedSecurityParams = persistence.UpsertListedSecurityParams{ + SecurityID: TestSecurity.ID, + Ticker: TestListedSecurity.Ticker, + Currency: TestListedSecurity.Currency, +} + +// TestBankAccount is a test bank account. +var TestBankAccount = &persistence.Account{ + ID: "myaccount", + DisplayName: "My Account", + Type: accounts.AccountTypeBank, +} + +// TestBrokerageAccount is a test security account. +var TestBrokerageAccount = &persistence.Account{ + ID: "mybrokerage", + DisplayName: "My Brokerage", + Type: accounts.AccountTypeBrokerage, +} + +// TestCreateBankAccountParams is a test bank account creation parameter for +// [TestBankAccount]. +var TestCreateBankAccountParams = persistence.CreateAccountParams{ + ID: TestBankAccount.ID, + DisplayName: TestBankAccount.DisplayName, + Type: TestBankAccount.Type, +} + +// TestCreateBrokerageAccountParams is a test brokerage account creation +// parameter for [TestBrokerageAccount]. +var TestCreateBrokerageAccountParams = persistence.CreateAccountParams{ + ID: TestBrokerageAccount.ID, + DisplayName: TestBrokerageAccount.DisplayName, + Type: TestBrokerageAccount.Type, +} + +// TestBuyTransaction is a test buy transaction of [TestSecurity]. The buy is +// initiated from [TestBankAccount] and the stocks are deposited in +// [TestBrokerageAccount]. +var TestBuyTransaction = &persistence.Transaction{ + ID: uuid.NewString(), + SourceAccountID: &TestBankAccount.ID, + DestinationAccountID: &TestBrokerageAccount.ID, + Time: time.Now(), + Type: events.PortfolioEventTypeBuy, + Amount: 100, + SecurityID: &TestSecurity.ID, + Price: currency.Value(100), +} + +// TestCreateBuyTransactionParams is a test buy transaction creation parameter +// for [TestBuyTransaction]. +var TestCreateBuyTransactionParams = persistence.CreateTransactionParams{ + ID: TestBuyTransaction.ID, + SourceAccountID: TestBuyTransaction.SourceAccountID, + DestinationAccountID: TestBuyTransaction.DestinationAccountID, + Time: TestBuyTransaction.Time, + Type: TestBuyTransaction.Type, + Amount: TestBuyTransaction.Amount, + SecurityID: TestBuyTransaction.SecurityID, + Price: TestBuyTransaction.Price, +} + +// TestDepositTransaction is a test deposit transaction. The deposit is made to +// [TestBankAccount]. +var TestDepositTransaction = &persistence.Transaction{ + ID: uuid.NewString(), + DestinationAccountID: &TestBankAccount.ID, + Time: time.Now(), + Type: events.PortfolioEventTypeBuy, + Amount: 1, + Price: currency.Value(100), +} + +// TestCreateDepositTransactionParams is a test deposit transaction creation +// parameter for [TestDepositTransaction]. +var TestCreateDepositTransactionParams = persistence.CreateTransactionParams{ + ID: TestDepositTransaction.ID, + DestinationAccountID: TestDepositTransaction.DestinationAccountID, + Time: TestDepositTransaction.Time, + Type: TestDepositTransaction.Type, + Amount: TestDepositTransaction.Amount, + Price: TestDepositTransaction.Price, +} + +// TestPortfolio is a test portfolio. +var TestPortfolio = &persistence.Portfolio{ + ID: "myportfolio", + DisplayName: "My Portfolio", +} + +// TestCreatePortfolioParams is a test portfolio creation parameter for +// [TestPortfolio]. +var TestCreatePortfolioParams = persistence.CreatePortfolioParams{ + ID: TestPortfolio.ID, + DisplayName: TestPortfolio.DisplayName, +} + +// TestAddAccountToPortfolioParams is a test account addition parameter for +// [TestBankAccount] to [TestPortfolio]. +var TestAddAccountToPortfolioParams = persistence.AddAccountToPortfolioParams{ + PortfolioID: TestPortfolio.ID, + AccountID: TestBankAccount.ID, +} diff --git a/internal/testing/persistencetest/queries.go b/internal/testing/persistencetest/queries.go new file mode 100644 index 00000000..41af36ad --- /dev/null +++ b/internal/testing/persistencetest/queries.go @@ -0,0 +1,24 @@ +package persistencetest + +import ( + "testing" + + "github.com/oxisto/money-gopher/persistence" +) + +func NewTestDB(t *testing.T, inits ...func(db *persistence.DB)) (db *persistence.DB) { + var ( + err error + ) + + db, err = persistence.OpenDB(persistence.Options{UseInMemory: true}) + if err != nil { + t.Fatalf("Could not create test DB: %v", err) + } + + for _, init := range inits { + init(db) + } + + return +} diff --git a/internal/testing/quotetest/quotetest.go b/internal/testing/quotetest/quotetest.go new file mode 100644 index 00000000..56b8fb36 --- /dev/null +++ b/internal/testing/quotetest/quotetest.go @@ -0,0 +1,24 @@ +package quotetest + +import ( + "context" + "time" + + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" +) + +const QuoteProviderStatic = "static" + +type StaticQuoteProvider struct { + Quote *currency.Currency +} + +// NewStaticQuoteProvider creates a new static quote provider that always returns the same quote. +func NewStaticQuoteProvider(quote *currency.Currency) *StaticQuoteProvider { + return &StaticQuoteProvider{Quote: quote} +} + +func (p *StaticQuoteProvider) LatestQuote(ctx context.Context, ls *persistence.ListedSecurity) (quote *currency.Currency, t time.Time, err error) { + return p.Quote, time.Now(), nil +} diff --git a/internal/testing/servertest/servertest.go b/internal/testing/servertest/servertest.go index 02489a99..603b0da5 100644 --- a/internal/testing/servertest/servertest.go +++ b/internal/testing/servertest/servertest.go @@ -4,10 +4,9 @@ import ( "net/http" "net/http/httptest" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" "github.com/oxisto/money-gopher/persistence" - "github.com/oxisto/money-gopher/service/portfolio" - "github.com/oxisto/money-gopher/service/securities" + "github.com/oxisto/money-gopher/securities/quote" + "github.com/oxisto/money-gopher/server" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" @@ -17,13 +16,9 @@ func NewServer(db *persistence.DB) *httptest.Server { mux := http.NewServeMux() srv := httptest.NewServer(h2c.NewHandler(mux, &http2.Server{})) - mux.Handle(portfoliov1connect.NewPortfolioServiceHandler(portfolio.NewService( - portfolio.Options{ - DB: db, - SecuritiesClient: portfoliov1connect.NewSecuritiesServiceClient(srv.Client(), srv.URL), - }, - ))) - mux.Handle(portfoliov1connect.NewSecuritiesServiceHandler(securities.NewService(db))) + qu := quote.NewQuoteUpdater(db) + + server.ConfigureGraphQL(mux, db, qu) return srv } diff --git a/mgo.proto b/mgo.proto deleted file mode 100644 index 3bfdaaf2..00000000 --- a/mgo.proto +++ /dev/null @@ -1,338 +0,0 @@ -syntax = "proto3"; - -package mgo.portfolio.v1; - -import "google/api/annotations.proto"; -import "google/api/field_behavior.proto"; -import "google/protobuf/empty.proto"; -import "google/protobuf/field_mask.proto"; -import "google/protobuf/timestamp.proto"; - -option go_package = "github.com/oxisto/money-gopher/gen;portfoliov1"; - -// Currency is a currency value in the lowest unit of the selected currency -// (e.g., cents for EUR/USD). -message Currency { - int32 value = 1 [(google.api.field_behavior) = REQUIRED]; - string symbol = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message CreatePortfolioRequest { - Portfolio portfolio = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message ListPortfoliosRequest {} -message ListPortfoliosResponse { - repeated Portfolio portfolios = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message GetPortfolioRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message UpdatePortfolioRequest { - Portfolio portfolio = 1 [(google.api.field_behavior) = REQUIRED]; - google.protobuf.FieldMask updateMask = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message DeletePortfolioRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message GetPortfolioSnapshotRequest { - // PortfolioId is the identifier of the portfolio we want to - // "snapshot". - string portfolio_id = 1 [(google.api.field_behavior) = REQUIRED]; - - // Time is the point in time of the requested snapshot. - google.protobuf.Timestamp time = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message CreatePortfolioTransactionRequest { - PortfolioEvent transaction = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message GetPortfolioTransactionRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message ListPortfolioTransactionsRequest { - string portfolio_id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message ListPortfolioTransactionsResponse { - repeated PortfolioEvent transactions = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message UpdatePortfolioTransactionRequest { - PortfolioEvent transaction = 1 [(google.api.field_behavior) = REQUIRED]; - google.protobuf.FieldMask updateMask = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message DeletePortfolioTransactionRequest { - int32 transaction_id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message ImportTransactionsRequest { - string portfolio_id = 1 [(google.api.field_behavior) = REQUIRED]; - string from_csv = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message CreateBankAccountRequest { - BankAccount bank_account = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message UpdateBankAccountRequest { - BankAccount account = 1 [(google.api.field_behavior) = REQUIRED]; - google.protobuf.FieldMask updateMask = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message DeleteBankAccountRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message Portfolio { - string id = 1 [(google.api.field_behavior) = REQUIRED]; - - string display_name = 2 [(google.api.field_behavior) = REQUIRED]; - - // BankAccountId contains the id/identifier of the underlying bank - // account. - string bank_account_id = 3 [(google.api.field_behavior) = REQUIRED]; - - // Events contains all portfolio events, such as buy/sell transactions, - // dividends or other. They need to be ordered by time (ascending). - repeated PortfolioEvent events = 5; -} - -message BankAccount { - string id = 1 [(google.api.field_behavior) = REQUIRED]; - - string display_name = 2 [(google.api.field_behavior) = REQUIRED]; -} - -// PortfolioSnapshot represents a snapshot in time of the portfolio. It can for -// example be the current state of the portfolio but also represent the state of -// the portfolio at a certain time in the past. -message PortfolioSnapshot { - // Time is the time when this snapshot was taken. - google.protobuf.Timestamp time = 1 [(google.api.field_behavior) = REQUIRED]; - - // Positions holds the current positions within the snapshot and their value. - map positions = 2 [(google.api.field_behavior) = REQUIRED]; - - // FirstTransactionTime is the time of the first transaction with the - // snapshot. - optional google.protobuf.Timestamp first_transaction_time = 3 [(google.api.field_behavior) = REQUIRED]; - - // TotalPurchaseValue contains the total purchase value of all asset positions - Currency total_purchase_value = 10 [(google.api.field_behavior) = REQUIRED]; - - // TotalMarketValue contains the total market value of all asset positions - Currency total_market_value = 11 [(google.api.field_behavior) = REQUIRED]; - - // TotalProfitOrLoss contains the total absolute amount of profit or loss in - // this snapshot, based on asset value. - Currency total_profit_or_loss = 20 [(google.api.field_behavior) = REQUIRED]; - - // TotalGains contains the total relative amount of profit or loss in this - // snapshot, based on asset value. - double total_gains = 21 [(google.api.field_behavior) = REQUIRED]; - - // Cash contains the current amount of cash in the portfolio's bank - // account(s). - Currency cash = 22 [(google.api.field_behavior) = REQUIRED]; - - // TotalPortfolioValue contains the amount of cash plus the total market value - // of all assets. - Currency total_portfolio_value = 23 [(google.api.field_behavior) = REQUIRED]; -} - -message PortfolioPosition { - Security security = 1 [(google.api.field_behavior) = REQUIRED]; - - double amount = 2 [(google.api.field_behavior) = REQUIRED]; - - // PurchaseValue was the market value of this position when it was bought - // (net; exclusive of any fees). - Currency purchase_value = 5 [(google.api.field_behavior) = REQUIRED]; - - // PurchasePrice was the market price of this position when it was bought - // (net; exclusive of any fees). - Currency purchase_price = 6 [(google.api.field_behavior) = REQUIRED]; - - // MarketValue is the current market value of this position, as retrieved from - // the securities service. - Currency market_value = 10 [(google.api.field_behavior) = REQUIRED]; - - // MarketPrice is the current market price of this position, as retrieved from - // the securities service. - Currency market_price = 11 [(google.api.field_behavior) = REQUIRED]; - - // TotalFees is the total amount of fees accumulating in this position through - // various transactions. - Currency total_fees = 15 [(google.api.field_behavior) = REQUIRED]; - - // ProfitOrLoss contains the absolute amount of profit or loss in this - // position. - Currency profit_or_loss = 20 [(google.api.field_behavior) = REQUIRED]; - - // Gains contains the relative amount of profit or loss in this position. - double gains = 21 [(google.api.field_behavior) = REQUIRED]; -} - -enum PortfolioEventType { - PORTFOLIO_EVENT_TYPE_UNSPECIFIED = 0; - - PORTFOLIO_EVENT_TYPE_BUY = 1; - PORTFOLIO_EVENT_TYPE_SELL = 2; - PORTFOLIO_EVENT_TYPE_DELIVERY_INBOUND = 3; - PORTFOLIO_EVENT_TYPE_DELIVERY_OUTBOUND = 4; - - PORTFOLIO_EVENT_TYPE_DIVIDEND = 10; - PORTFOLIO_EVENT_TYPE_INTEREST = 11; - - PORTFOLIO_EVENT_TYPE_DEPOSIT_CASH = 20; - PORTFOLIO_EVENT_TYPE_WITHDRAW_CASH = 21; - - PORTFOLIO_EVENT_TYPE_ACCOUNT_FEES = 30; - PORTFOLIO_EVENT_TYPE_TAX_REFUND = 31; -} - -message PortfolioEvent { - string id = 1 [(google.api.field_behavior) = REQUIRED]; - PortfolioEventType type = 2 [(google.api.field_behavior) = REQUIRED]; - google.protobuf.Timestamp time = 3 [(google.api.field_behavior) = REQUIRED]; - string portfolio_id = 4 [(google.api.field_behavior) = REQUIRED]; - string security_id = 5 [(google.api.field_behavior) = REQUIRED]; - - double amount = 10 [(google.api.field_behavior) = REQUIRED]; - Currency price = 11 [(google.api.field_behavior) = REQUIRED]; - Currency fees = 12 [(google.api.field_behavior) = REQUIRED]; - Currency taxes = 13 [(google.api.field_behavior) = REQUIRED]; -} - -service PortfolioService { - rpc CreatePortfolio(CreatePortfolioRequest) returns (Portfolio) { - option (google.api.http) = { - post: "/v1/portfolios" - body: "portfolio" - }; - } - rpc ListPortfolios(ListPortfoliosRequest) returns (ListPortfoliosResponse) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/portfolios"}; - } - rpc GetPortfolio(GetPortfolioRequest) returns (Portfolio) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/portfolios/{id}"}; - } - rpc UpdatePortfolio(UpdatePortfolioRequest) returns (Portfolio); - rpc DeletePortfolio(DeletePortfolioRequest) returns (google.protobuf.Empty); - - rpc GetPortfolioSnapshot(GetPortfolioSnapshotRequest) returns (PortfolioSnapshot) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/portfolios/{portfolio_id}/snapshot"}; - } - - rpc CreatePortfolioTransaction(CreatePortfolioTransactionRequest) returns (PortfolioEvent) { - option (google.api.http) = { - post: "/v1/portfolios/{transaction.portfolio_id}/transactions" - body: "transaction" - }; - } - rpc GetPortfolioTransaction(GetPortfolioTransactionRequest) returns (PortfolioEvent) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/transactions/{id}"}; - } - rpc ListPortfolioTransactions(ListPortfolioTransactionsRequest) returns (ListPortfolioTransactionsResponse) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/portfolios/{portfolio_id}/transactions"}; - } - rpc UpdatePortfolioTransaction(UpdatePortfolioTransactionRequest) returns (PortfolioEvent) { - option (google.api.http) = { - put: "/v1/transactions/{transaction.id}" - body: "transaction" - }; - } - rpc DeletePortfolioTransaction(DeletePortfolioTransactionRequest) returns (google.protobuf.Empty); - - rpc ImportTransactions(ImportTransactionsRequest) returns (google.protobuf.Empty); - - rpc CreateBankAccount(CreateBankAccountRequest) returns (BankAccount); - rpc UpdateBankAccount(UpdateBankAccountRequest) returns (BankAccount); - rpc DeleteBankAccount(DeleteBankAccountRequest) returns (google.protobuf.Empty); -} - -message Security { - // Id contains the unique resource ID. For a stock or bond, this should be - // an ISIN. - string id = 1 [(google.api.field_behavior) = REQUIRED]; - - // DisplayName contains the human readable id. - string display_name = 2 [(google.api.field_behavior) = REQUIRED]; - - repeated ListedSecurity listed_on = 4 [(google.api.field_behavior) = REQUIRED]; - - optional string quote_provider = 10 [(google.api.field_behavior) = REQUIRED]; -} - -message ListedSecurity { - string security_id = 1 [(google.api.field_behavior) = REQUIRED]; - string ticker = 3 [(google.api.field_behavior) = REQUIRED]; - string currency = 4 [(google.api.field_behavior) = REQUIRED]; - - optional Currency latest_quote = 5; - optional google.protobuf.Timestamp latest_quote_timestamp = 6; -} - -message ListSecuritiesRequest { - message Filter { - repeated string security_ids = 1; - } - - optional Filter filter = 5; -} - -message ListSecuritiesResponse { - repeated Security securities = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message GetSecurityRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message CreateSecurityRequest { - Security security = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message UpdateSecurityRequest { - Security security = 1 [(google.api.field_behavior) = REQUIRED]; - google.protobuf.FieldMask updateMask = 2 [(google.api.field_behavior) = REQUIRED]; -} - -message DeleteSecurityRequest { - string id = 1 [(google.api.field_behavior) = REQUIRED]; -} - -message TriggerQuoteUpdateRequest { - repeated string security_ids = 1; -} - -message TriggerQuoteUpdateResponse {} - -service SecuritiesService { - rpc ListSecurities(ListSecuritiesRequest) returns (ListSecuritiesResponse) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/securities"}; - } - rpc GetSecurity(GetSecurityRequest) returns (Security) { - option idempotency_level = NO_SIDE_EFFECTS; - option (google.api.http) = {get: "/v1/securities/{id}"}; - } - rpc CreateSecurity(CreateSecurityRequest) returns (Security); - rpc UpdateSecurity(UpdateSecurityRequest) returns (Security); - rpc DeleteSecurity(DeleteSecurityRequest) returns (google.protobuf.Empty); - - rpc TriggerSecurityQuoteUpdate(TriggerQuoteUpdateRequest) returns (TriggerQuoteUpdateResponse); -} diff --git a/models/models_gen.go b/models/models_gen.go new file mode 100644 index 00000000..b4dfa943 --- /dev/null +++ b/models/models_gen.go @@ -0,0 +1,100 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package models + +import ( + "time" + + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" +) + +type AccountInput struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + Type accounts.AccountType `json:"type"` +} + +type CurrencyInput struct { + Amount int `json:"amount"` + Symbol string `json:"symbol"` +} + +type ListedSecurityInput struct { + Ticker string `json:"ticker"` + Currency string `json:"currency"` +} + +type Mutation struct { +} + +type PortfolioEvent struct { + Time time.Time `json:"time"` + Type events.PortfolioEventType `json:"type"` + Security *persistence.Security `json:"security,omitempty"` +} + +type PortfolioInput struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + AccountIds []string `json:"accountIds"` +} + +type PortfolioPosition struct { + Security *persistence.Security `json:"security"` + Amount float64 `json:"amount"` + // PurchaseValue was the market value of this position when it was bought (net; + // exclusive of any fees). + PurchaseValue *currency.Currency `json:"purchaseValue"` + // PurchasePrice was the market price of this position when it was bought (net; + // exclusive of any fees). + PurchasePrice *currency.Currency `json:"purchasePrice"` + // MarketValue is the current market value of this position, as retrieved from + // the securities service. + MarketValue *currency.Currency `json:"marketValue"` + // MarketPrice is the current market price of this position, as retrieved from + // the securities service. + MarketPrice *currency.Currency `json:"marketPrice"` + // TotalFees is the total amount of fees accumulating in this position through + // various transactions. + TotalFees *currency.Currency `json:"totalFees"` + // ProfitOrLoss contains the absolute amount of profit or loss in this position. + ProfitOrLoss *currency.Currency `json:"profitOrLoss"` + // Gains contains the relative amount of profit or loss in this position. + Gains float64 `json:"gains"` +} + +type PortfolioSnapshot struct { + Time time.Time `json:"time"` + Positions []*PortfolioPosition `json:"positions"` + FirstTransactionTime time.Time `json:"firstTransactionTime"` + TotalPurchaseValue *currency.Currency `json:"totalPurchaseValue"` + TotalMarketValue *currency.Currency `json:"totalMarketValue"` + TotalProfitOrLoss *currency.Currency `json:"totalProfitOrLoss"` + TotalGains float64 `json:"totalGains"` + TotalPortfolioValue *currency.Currency `json:"totalPortfolioValue,omitempty"` + Cash *currency.Currency `json:"cash"` +} + +type Query struct { +} + +type SecurityInput struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + ListedAs []*ListedSecurityInput `json:"listedAs,omitempty"` +} + +type TransactionInput struct { + Time time.Time `json:"time"` + SourceAccountID string `json:"sourceAccountID"` + DestinationAccountID string `json:"destinationAccountID"` + SecurityID string `json:"securityID"` + Amount float64 `json:"amount"` + Price *CurrencyInput `json:"price"` + Fees *CurrencyInput `json:"fees"` + Taxes *CurrencyInput `json:"taxes"` + Type events.PortfolioEventType `json:"type"` +} diff --git a/money-gopher.code-workspace b/money-gopher.code-workspace index 5405857b..722ac7fd 100644 --- a/money-gopher.code-workspace +++ b/money-gopher.code-workspace @@ -9,45 +9,61 @@ ], "settings": { "cSpell.words": [ + "agnivade", "Banse", "bufbuild", "clitest", "connectrpc", + "DBTX", "emptypb", + "errgroup", "fatih", "fieldmaskpb", + "gqlgen", + "gqlparser", "headlessui", "heroicons", "isatty", "ISIN", "jotaen", "kongcompletion", + "linecomment", "lmittmann", + "mapstructure", + "mattn", "mcli", "mfridman", "modernc", "moneyd", "moneygopher", + "multierr", + "myaccount", "mybank", "mycash", "myportfolio", "mysecurity", "oxisto", + "persistencetest", "portfoliotest", "portfoliov", - "protobuf", - "protocmp", - "protoreflect", + "pressly", + "quotetest", "rgossiaux", "secmap", "secres", "servertest", + "sethvargo", + "shurcoo", "sidebaritems", + "sosodev", "sqlc", "steeze", "tailwindcss", "timestamppb", - "tparse" + "tparse", + "unmarshals", + "urfave", + "vektah" ], "editor.tabSize": 4, "typescript.tsdk": "ui-next/node_modules/typescript/lib", diff --git a/persistence/accounts.sql.go b/persistence/accounts.sql.go new file mode 100644 index 00000000..b30036e7 --- /dev/null +++ b/persistence/accounts.sql.go @@ -0,0 +1,426 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: accounts.sql + +package persistence + +import ( + "context" + "time" + + currency "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" +) + +const addAccountToPortfolio = `-- name: AddAccountToPortfolio :exec +INSERT INTO + portfolio_accounts (portfolio_id, account_id) +VALUES + (?, ?) +` + +type AddAccountToPortfolioParams struct { + PortfolioID string + AccountID string +} + +func (q *Queries) AddAccountToPortfolio(ctx context.Context, arg AddAccountToPortfolioParams) error { + _, err := q.db.ExecContext(ctx, addAccountToPortfolio, arg.PortfolioID, arg.AccountID) + return err +} + +const createAccount = `-- name: CreateAccount :one +INSERT INTO + accounts (id, display_name, type, reference_account_id) +VALUES + (?, ?, ?, ?) RETURNING id, display_name, type, reference_account_id +` + +type CreateAccountParams struct { + ID string + DisplayName string + Type accounts.AccountType + ReferenceAccountID *int64 +} + +func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (*Account, error) { + row := q.db.QueryRowContext(ctx, createAccount, + arg.ID, + arg.DisplayName, + arg.Type, + arg.ReferenceAccountID, + ) + var i Account + err := row.Scan( + &i.ID, + &i.DisplayName, + &i.Type, + &i.ReferenceAccountID, + ) + return &i, err +} + +const createPortfolio = `-- name: CreatePortfolio :one +INSERT INTO + portfolios (id, display_name) +VALUES + (?, ?) RETURNING id, display_name +` + +type CreatePortfolioParams struct { + ID string + DisplayName string +} + +func (q *Queries) CreatePortfolio(ctx context.Context, arg CreatePortfolioParams) (*Portfolio, error) { + row := q.db.QueryRowContext(ctx, createPortfolio, arg.ID, arg.DisplayName) + var i Portfolio + err := row.Scan(&i.ID, &i.DisplayName) + return &i, err +} + +const createTransaction = `-- name: CreateTransaction :one +INSERT INTO + transactions ( + id, + source_account_id, + destination_account_id, + time, + type, + security_id, + amount, + price, + fees, + taxes + ) +VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id, source_account_id, destination_account_id, time, type, security_id, amount, price, fees, taxes +` + +type CreateTransactionParams struct { + ID string + SourceAccountID *string + DestinationAccountID *string + Time time.Time + Type events.PortfolioEventType + SecurityID *string + Amount float64 + Price *currency.Currency + Fees *currency.Currency + Taxes *currency.Currency +} + +func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (*Transaction, error) { + row := q.db.QueryRowContext(ctx, createTransaction, + arg.ID, + arg.SourceAccountID, + arg.DestinationAccountID, + arg.Time, + arg.Type, + arg.SecurityID, + arg.Amount, + arg.Price, + arg.Fees, + arg.Taxes, + ) + var i Transaction + err := row.Scan( + &i.ID, + &i.SourceAccountID, + &i.DestinationAccountID, + &i.Time, + &i.Type, + &i.SecurityID, + &i.Amount, + &i.Price, + &i.Fees, + &i.Taxes, + ) + return &i, err +} + +const deleteAccount = `-- name: DeleteAccount :one +DELETE FROM accounts +WHERE + id = ? RETURNING id, display_name, type, reference_account_id +` + +func (q *Queries) DeleteAccount(ctx context.Context, id string) (*Account, error) { + row := q.db.QueryRowContext(ctx, deleteAccount, id) + var i Account + err := row.Scan( + &i.ID, + &i.DisplayName, + &i.Type, + &i.ReferenceAccountID, + ) + return &i, err +} + +const getAccount = `-- name: GetAccount :one +SELECT + id, display_name, type, reference_account_id +FROM + accounts +WHERE + id = ? +` + +func (q *Queries) GetAccount(ctx context.Context, id string) (*Account, error) { + row := q.db.QueryRowContext(ctx, getAccount, id) + var i Account + err := row.Scan( + &i.ID, + &i.DisplayName, + &i.Type, + &i.ReferenceAccountID, + ) + return &i, err +} + +const getPortfolio = `-- name: GetPortfolio :one +SELECT + id, display_name +FROM + portfolios +WHERE + id = ? +` + +func (q *Queries) GetPortfolio(ctx context.Context, id string) (*Portfolio, error) { + row := q.db.QueryRowContext(ctx, getPortfolio, id) + var i Portfolio + err := row.Scan(&i.ID, &i.DisplayName) + return &i, err +} + +const listAccounts = `-- name: ListAccounts :many +SELECT + id, display_name, type, reference_account_id +FROM + accounts +ORDER BY + id +` + +func (q *Queries) ListAccounts(ctx context.Context) ([]*Account, error) { + rows, err := q.db.QueryContext(ctx, listAccounts) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Account + for rows.Next() { + var i Account + if err := rows.Scan( + &i.ID, + &i.DisplayName, + &i.Type, + &i.ReferenceAccountID, + ); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAccountsByPortfolioID = `-- name: ListAccountsByPortfolioID :many +SELECT + accounts.id, accounts.display_name, accounts.type, accounts.reference_account_id +FROM + accounts + JOIN portfolio_accounts ON accounts.id = portfolio_accounts.account_id +WHERE + portfolio_accounts.portfolio_id = ? +` + +func (q *Queries) ListAccountsByPortfolioID(ctx context.Context, portfolioID string) ([]*Account, error) { + rows, err := q.db.QueryContext(ctx, listAccountsByPortfolioID, portfolioID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Account + for rows.Next() { + var i Account + if err := rows.Scan( + &i.ID, + &i.DisplayName, + &i.Type, + &i.ReferenceAccountID, + ); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listPortfolios = `-- name: ListPortfolios :many +SELECT + id, display_name +FROM + portfolios +ORDER BY + id +` + +func (q *Queries) ListPortfolios(ctx context.Context) ([]*Portfolio, error) { + rows, err := q.db.QueryContext(ctx, listPortfolios) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Portfolio + for rows.Next() { + var i Portfolio + if err := rows.Scan(&i.ID, &i.DisplayName); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listTransactionsByAccountID = `-- name: ListTransactionsByAccountID :many +SELECT + id, source_account_id, destination_account_id, time, type, security_id, amount, price, fees, taxes +FROM + transactions +WHERE + source_account_id = ?1 + OR destination_account_id = ?1 +` + +func (q *Queries) ListTransactionsByAccountID(ctx context.Context, accountID *string) ([]*Transaction, error) { + rows, err := q.db.QueryContext(ctx, listTransactionsByAccountID, accountID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.SourceAccountID, + &i.DestinationAccountID, + &i.Time, + &i.Type, + &i.SecurityID, + &i.Amount, + &i.Price, + &i.Fees, + &i.Taxes, + ); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listTransactionsByPortfolioID = `-- name: ListTransactionsByPortfolioID :many +SELECT + id, source_account_id, destination_account_id, time, type, security_id, amount, price, fees, taxes +FROM + transactions +WHERE + source_account_id IN ( + SELECT + account_id + FROM + portfolio_accounts + WHERE + portfolio_accounts.portfolio_id = ?1 + ) + OR destination_account_id IN ( + SELECT + account_id + FROM + portfolio_accounts + WHERE + portfolio_accounts.portfolio_id = ?1 + ) +` + +func (q *Queries) ListTransactionsByPortfolioID(ctx context.Context, portfolioID string) ([]*Transaction, error) { + rows, err := q.db.QueryContext(ctx, listTransactionsByPortfolioID, portfolioID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.SourceAccountID, + &i.DestinationAccountID, + &i.Time, + &i.Type, + &i.SecurityID, + &i.Amount, + &i.Price, + &i.Fees, + &i.Taxes, + ); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updatePortfolio = `-- name: UpdatePortfolio :one +UPDATE portfolios +SET + display_name = ? +WHERE + id = ? RETURNING id, display_name +` + +type UpdatePortfolioParams struct { + DisplayName string + ID string +} + +func (q *Queries) UpdatePortfolio(ctx context.Context, arg UpdatePortfolioParams) (*Portfolio, error) { + row := q.db.QueryRowContext(ctx, updatePortfolio, arg.DisplayName, arg.ID) + var i Portfolio + err := row.Scan(&i.ID, &i.DisplayName) + return &i, err +} diff --git a/persistence/extra.go b/persistence/extra.go new file mode 100644 index 00000000..9ebd50a3 --- /dev/null +++ b/persistence/extra.go @@ -0,0 +1,31 @@ +package persistence + +import ( + "context" + "hash/fnv" + "strconv" + "time" +) + +// ListedAs returns the listed securities for the security. +func (s *Security) ListedAs(ctx context.Context, db *DB) ([]*ListedSecurity, error) { + return db.ListListedSecuritiesBySecurityID(ctx, s.ID) + +} + +// MakeUniqueID creates a unique ID for the portfolio event based on a hash containing: +// - security ID +// - portfolio ID +// - date +// - amount +func (tx *Transaction) MakeUniqueID() { + h := fnv.New64a() + h.Write([]byte(*tx.SecurityID)) + h.Write([]byte(*tx.SourceAccountID)) + h.Write([]byte(*tx.DestinationAccountID)) + h.Write([]byte(tx.Time.Format(time.DateTime))) + h.Write([]byte(strconv.FormatInt(int64(tx.Type), 10))) + h.Write([]byte(strconv.FormatInt(int64(tx.Amount), 10))) + + tx.ID = strconv.FormatUint(h.Sum64(), 16) +} diff --git a/persistence/models.go b/persistence/models.go index c83e9d6c..ff45e158 100644 --- a/persistence/models.go +++ b/persistence/models.go @@ -5,9 +5,25 @@ package persistence import ( - "database/sql" + "time" + + currency "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/portfolio/accounts" + "github.com/oxisto/money-gopher/portfolio/events" ) +// Accounts represents an account, such as a brokerage account or a bank +type Account struct { + // ID is the primary identifier for a brokerage account. + ID string + // DisplayName is the human-readable name of the brokerage account. + DisplayName string + // Type is the type of the account. + Type accounts.AccountType + // ReferenceAccountID is the ID of the account that this account is related to. For example, if this is a brokerage account, the reference account could be a bank account. + ReferenceAccountID *int64 +} + // ListedSecurity represents a security that is listed on a particular exchange. type ListedSecurity struct { // SecurityID is the ID of the security. @@ -16,10 +32,24 @@ type ListedSecurity struct { Ticker string // Currency is the currency in which the security is traded. Currency string - // LatestQuote is the latest quote for the security. - LatestQuote sql.NullInt64 + // LatestQuote is the latest quote for the security as a [currency.Currency]. + LatestQuote *currency.Currency // LatestQuoteTimestamp is the timestamp of the latest quote. - LatestQuoteTimestamp sql.NullTime + LatestQuoteTimestamp *time.Time +} + +// Portfolios represent a collection of securities and other positions. +type Portfolio struct { + // ID is the primary identifier for a portfolio. + ID string + // DisplayName is the human-readable name of the portfolio. + DisplayName string +} + +// PortfolioAccounts represents the relationship between portfolios and accounts. +type PortfolioAccount struct { + PortfolioID string + AccountID string } // Security represents a security that can be traded on an exchange. @@ -29,5 +59,29 @@ type Security struct { // DisplayName is the human-readable name of the security. DisplayName string // QuoteProvider is the name of the provider that provides quotes for this security. - QuoteProvider sql.NullString + QuoteProvider *string +} + +// Transactions represents a transaction in an account. +type Transaction struct { + // ID is the primary identifier for a transaction. + ID string + // SourceAccountID is the ID of the account that the transaction originated from. + SourceAccountID *string + // DestinationAccountID is the ID of the account that the transaction is destined for. + DestinationAccountID *string + // Time is the time that the transaction occurred. + Time time.Time + // Type is the type of the transaction. Depending on the type, different fields (source, destination) will be used. + Type events.PortfolioEventType + // SecurityID is the ID of the security that the transaction is related to. Can be empty if the transaction is not related to a security. + SecurityID *string + // Amount is the amount of the transaction. + Amount float64 + // Price is the price of the transaction. + Price *currency.Currency + // Fees is the fees of the transaction. + Fees *currency.Currency + // Taxes is the taxes of the transaction. + Taxes *currency.Currency } diff --git a/persistence/persistence.go b/persistence/persistence.go index f3d91511..7f77ff36 100644 --- a/persistence/persistence.go +++ b/persistence/persistence.go @@ -49,7 +49,10 @@ func (o Options) LogValue() slog.Value { } // DB is a type alias around [sql.DB] to avoid importing the [database/sql] package. -type DB = sql.DB +type DB struct { + *Queries + *sql.DB +} type StorageObject interface { InitTables(db *DB) (err error) @@ -80,22 +83,23 @@ type ops[T StorageObject] struct { } // OpenDB opens a connection to our database. -func OpenDB(opts Options) (db *DB, q *Queries, err error) { +func OpenDB(opts Options) (db *DB, err error) { if opts.UseInMemory { opts.DSN = ":memory:?_pragma=foreign_keys(1)" } else if opts.DSN == "" { opts.DSN = "money.db" } - db, err = sql.Open("sqlite3", opts.DSN) + inner, err := sql.Open("sqlite3", opts.DSN) if err != nil { - return nil, nil, fmt.Errorf("could not open database: %w", err) + return nil, fmt.Errorf("could not open database: %w", err) } + db = &DB{Queries: New(inner), DB: inner} slog.Info("Successfully opened database connection", "opts", opts) // Prepare database migrations with goose - provider, err := goose.NewProvider(database.DialectSQLite3, db, migrations.Embed) + provider, err := goose.NewProvider(database.DialectSQLite3, db.DB, migrations.Embed) if err != nil { log.Fatal(err) } @@ -103,16 +107,13 @@ func OpenDB(opts Options) (db *DB, q *Queries, err error) { // Apply all migrations results, err := provider.Up(context.Background()) if err != nil { - return nil, nil, err + return nil, err } for _, result := range results { slog.Debug("Applied migration.", "migration", result) } - // Create a new query object - q = New(db) - return } diff --git a/persistence/persistence_test.go b/persistence/persistence_test.go index 2387ed38..76447829 100644 --- a/persistence/persistence_test.go +++ b/persistence/persistence_test.go @@ -54,7 +54,7 @@ func TestOpenDB(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotDb, _, err := OpenDB(tt.args.opts) + gotDb, err := OpenDB(tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("OpenDB() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/persistence/securities.sql.go b/persistence/securities.sql.go index 7718c7a2..72432ca6 100644 --- a/persistence/securities.sql.go +++ b/persistence/securities.sql.go @@ -7,23 +7,27 @@ package persistence import ( "context" - "database/sql" + "strings" + "time" + + currency "github.com/oxisto/money-gopher/currency" ) const createSecurity = `-- name: CreateSecurity :one INSERT INTO - securities (id, display_name) + securities (id, display_name, quote_provider) VALUES - (?, ?) RETURNING id, display_name, quote_provider + (?, ?, ?) RETURNING id, display_name, quote_provider ` type CreateSecurityParams struct { - ID string - DisplayName string + ID string + DisplayName string + QuoteProvider *string } func (q *Queries) CreateSecurity(ctx context.Context, arg CreateSecurityParams) (*Security, error) { - row := q.db.QueryRowContext(ctx, createSecurity, arg.ID, arg.DisplayName) + row := q.db.QueryRowContext(ctx, createSecurity, arg.ID, arg.DisplayName, arg.QuoteProvider) var i Security err := row.Scan(&i.ID, &i.DisplayName, &i.QuoteProvider) return &i, err @@ -142,6 +146,50 @@ func (q *Queries) ListSecurities(ctx context.Context) ([]*Security, error) { return items, nil } +const listSecuritiesByIDs = `-- name: ListSecuritiesByIDs :many +SELECT + id, display_name, quote_provider +FROM + securities +WHERE + id IN (/*SLICE:ids*/?) +ORDER BY + id +` + +func (q *Queries) ListSecuritiesByIDs(ctx context.Context, ids []string) ([]*Security, error) { + query := listSecuritiesByIDs + var queryParams []interface{} + if len(ids) > 0 { + for _, v := range ids { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:ids*/?", strings.Repeat(",?", len(ids))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) + } + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []*Security + for rows.Next() { + var i Security + if err := rows.Scan(&i.ID, &i.DisplayName, &i.QuoteProvider); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateSecurity = `-- name: UpdateSecurity :one UPDATE securities SET @@ -153,7 +201,7 @@ WHERE type UpdateSecurityParams struct { DisplayName string - QuoteProvider sql.NullString + QuoteProvider *string ID string } @@ -166,23 +214,39 @@ func (q *Queries) UpdateSecurity(ctx context.Context, arg UpdateSecurityParams) const upsertListedSecurity = `-- name: UpsertListedSecurity :one INSERT INTO - listed_securities (security_id, ticker, currency) + listed_securities ( + security_id, + ticker, + currency, + latest_quote, + latest_quote_timestamp + ) VALUES - (?, ?, ?) ON CONFLICT (security_id, ticker) DO + (?, ?, ?, ?, ?) ON CONFLICT (security_id, ticker) DO UPDATE SET ticker = excluded.ticker, - currency = excluded.currency RETURNING security_id, ticker, currency, latest_quote, latest_quote_timestamp + currency = excluded.currency, + latest_quote = excluded.latest_quote, + latest_quote_timestamp = excluded.latest_quote_timestamp RETURNING security_id, ticker, currency, latest_quote, latest_quote_timestamp ` type UpsertListedSecurityParams struct { - SecurityID string - Ticker string - Currency string + SecurityID string + Ticker string + Currency string + LatestQuote *currency.Currency + LatestQuoteTimestamp *time.Time } func (q *Queries) UpsertListedSecurity(ctx context.Context, arg UpsertListedSecurityParams) (*ListedSecurity, error) { - row := q.db.QueryRowContext(ctx, upsertListedSecurity, arg.SecurityID, arg.Ticker, arg.Currency) + row := q.db.QueryRowContext(ctx, upsertListedSecurity, + arg.SecurityID, + arg.Ticker, + arg.Currency, + arg.LatestQuote, + arg.LatestQuoteTimestamp, + ) var i ListedSecurity err := row.Scan( &i.SecurityID, diff --git a/persistence/securities.sql_test.go b/persistence/securities.sql_test.go new file mode 100644 index 00000000..bb9f03d8 --- /dev/null +++ b/persistence/securities.sql_test.go @@ -0,0 +1,70 @@ +package persistence_test + +import ( + "context" + "testing" + + "github.com/oxisto/assert" + "github.com/oxisto/money-gopher/internal/testing/persistencetest" + "github.com/oxisto/money-gopher/persistence" +) + +func TestQueries_ListListedSecuritiesBySecurityID(t *testing.T) { + type fields struct { + db persistence.DBTX + } + type args struct { + ctx context.Context + id string + } + tests := []struct { + name string + fields fields + args args + want []*persistence.ListedSecurity + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + db: persistencetest.NewTestDB(t, func(db *persistence.DB) { + _, err := db.Queries.CreateSecurity(context.Background(), persistence.CreateSecurityParams{ + ID: "DE1234567890", + DisplayName: "My Security", + }) + assert.NoError(t, err) + + _, err = db.Queries.UpsertListedSecurity(context.Background(), persistence.UpsertListedSecurityParams{ + SecurityID: "DE1234567890", + Ticker: "TICK", + Currency: "USD", + }) + assert.NoError(t, err) + }), + }, + args: args{ + ctx: context.TODO(), + id: "DE1234567890", + }, + want: []*persistence.ListedSecurity{ + { + SecurityID: "DE1234567890", + Ticker: "TICK", + Currency: "USD", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + q := persistence.New(tt.fields.db) + got, err := q.ListListedSecuritiesBySecurityID(tt.args.ctx, tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("Queries.ListListedSecuritiesBySecurityID() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equals(t, tt.want, got) + }) + } +} diff --git a/persistence/sql/migrations/0001_create_securities.sql b/persistence/sql/migrations/0001_create_securities.sql index 10ab88a3..e8e5fb46 100644 --- a/persistence/sql/migrations/0001_create_securities.sql +++ b/persistence/sql/migrations/0001_create_securities.sql @@ -13,7 +13,7 @@ CREATE TABLE security_id TEXT NOT NULL, -- SecurityID is the ID of the security. ticker TEXT NOT NULL, -- Ticker is the symbol used to identify the security on the exchange. currency TEXT NOT NULL, -- Currency is the currency in which the security is traded. - latest_quote INTEGER, -- LatestQuote is the latest quote for the security. + latest_quote JSONB, -- LatestQuote is the latest quote for the security as a [currency.Currency]. latest_quote_timestamp DATETIME, -- LatestQuoteTimestamp is the timestamp of the latest quote. FOREIGN KEY (security_id) REFERENCES securities (id) ON DELETE RESTRICT, PRIMARY KEY (security_id, ticker) @@ -21,4 +21,5 @@ CREATE TABLE -- +goose Down DROP TABLE securities; -DROP TABLE listed_securities; + +DROP TABLE listed_securities; \ No newline at end of file diff --git a/persistence/sql/migrations/0002_create_accounts.sql b/persistence/sql/migrations/0002_create_accounts.sql new file mode 100644 index 00000000..f14867af --- /dev/null +++ b/persistence/sql/migrations/0002_create_accounts.sql @@ -0,0 +1,56 @@ +-- +goose Up +CREATE TABLE + IF NOT EXISTS portfolios ( + -- Portfolios represent a collection of securities and other positions. + -- held by a user. It can be seen as read-only view over multiple + -- accounts. + id TEXT PRIMARY KEY, -- ID is the primary identifier for a portfolio. + display_name TEXT NOT NULL -- DisplayName is the human-readable name of the portfolio. + ); + +CREATE TABLE + IF NOT EXISTS accounts ( + -- Accounts represents an account, such as a brokerage account or a bank + -- account which comprise a portfolio. + id TEXT PRIMARY KEY, -- ID is the primary identifier for a brokerage account. + display_name TEXT NOT NULL, -- DisplayName is the human-readable name of the brokerage account. + type INTEGER NOT NULL, -- Type is the type of the account. + reference_account_id INTEGER, -- ReferenceAccountID is the ID of the account that this account is related to. For example, if this is a brokerage account, the reference account could be a bank account. + FOREIGN KEY (reference_account_id) REFERENCES accounts (id) ON DELETE RESTRICT + ); + +CREATE TABLE + IF NOT EXISTS transactions ( + -- Transactions represents a transaction in an account. + id TEXT PRIMARY KEY, -- ID is the primary identifier for a transaction. + source_account_id TEXT, -- SourceAccountID is the ID of the account that the transaction originated from. + destination_account_id TEXT, -- DestinationAccountID is the ID of the account that the transaction is destined for. + time DATETIME NOT NULL, -- Time is the time that the transaction occurred. + type INTEGER NOT NULL, -- Type is the type of the transaction. Depending on the type, different fields (source, destination) will be used. + security_id TEXT, -- SecurityID is the ID of the security that the transaction is related to. Can be empty if the transaction is not related to a security. + amount REAL NOT NULL, -- Amount is the amount of the transaction. + price JSONB, -- Price is the price of the transaction. + fees JSONB, -- Fees is the fees of the transaction. + taxes JSONB, -- Taxes is the taxes of the transaction. + FOREIGN KEY (source_account_id) REFERENCES accounts (id) ON DELETE RESTRICT, + FOREIGN KEY (destination_account_id) REFERENCES accounts (id) ON DELETE RESTRICT + ); + +CREATE TABLE + IF NOT EXISTS portfolio_accounts ( + -- PortfolioAccounts represents the relationship between portfolios and accounts. + portfolio_id TEXT NOT NULL, + account_id TEXT NOT NULL, + FOREIGN KEY (portfolio_id) REFERENCES portfolios (id) ON DELETE RESTRICT, + FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE RESTRICT, + PRIMARY KEY (account_id, portfolio_id) + ); + +-- +goose Down +DROP TABLE portfolios; + +DROP TABLE portfolio_accounts; + +DROP TABLE accounts; + +DROP TABLE transactions; \ No newline at end of file diff --git a/persistence/sql/queries/accounts.sql b/persistence/sql/queries/accounts.sql new file mode 100644 index 00000000..efa4fbc0 --- /dev/null +++ b/persistence/sql/queries/accounts.sql @@ -0,0 +1,119 @@ +-- name: GetPortfolio :one +SELECT + * +FROM + portfolios +WHERE + id = ?; + +-- name: ListPortfolios :many +SELECT + * +FROM + portfolios +ORDER BY + id; + +-- name: ListAccounts :many +SELECT + * +FROM + accounts +ORDER BY + id; + +-- name: GetAccount :one +SELECT + * +FROM + accounts +WHERE + id = ?; + +-- name: CreateAccount :one +INSERT INTO + accounts (id, display_name, type, reference_account_id) +VALUES + (?, ?, ?, ?) RETURNING *; + +-- name: DeleteAccount :one +DELETE FROM accounts +WHERE + id = ? RETURNING *; + +-- name: CreatePortfolio :one +INSERT INTO + portfolios (id, display_name) +VALUES + (?, ?) RETURNING *; + +-- name: UpdatePortfolio :one +UPDATE portfolios +SET + display_name = ? +WHERE + id = ? RETURNING *; + +-- name: AddAccountToPortfolio :exec +INSERT INTO + portfolio_accounts (portfolio_id, account_id) +VALUES + (?, ?); + +-- name: CreateTransaction :one +INSERT INTO + transactions ( + id, + source_account_id, + destination_account_id, + time, + type, + security_id, + amount, + price, + fees, + taxes + ) +VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *; + +-- name: ListAccountsByPortfolioID :many +SELECT + accounts.* +FROM + accounts + JOIN portfolio_accounts ON accounts.id = portfolio_accounts.account_id +WHERE + portfolio_accounts.portfolio_id = ?; + +-- name: ListTransactionsByAccountID :many +SELECT + * +FROM + transactions +WHERE + source_account_id = sqlc.arg ('account_id') + OR destination_account_id = sqlc.arg ('account_id'); + +-- name: ListTransactionsByPortfolioID :many +SELECT + * +FROM + transactions +WHERE + source_account_id IN ( + SELECT + account_id + FROM + portfolio_accounts + WHERE + portfolio_accounts.portfolio_id = sqlc.arg ('portfolio_id') + ) + OR destination_account_id IN ( + SELECT + account_id + FROM + portfolio_accounts + WHERE + portfolio_accounts.portfolio_id = sqlc.arg ('portfolio_id') + ); \ No newline at end of file diff --git a/persistence/sql/queries/securities.sql b/persistence/sql/queries/securities.sql index 9deec40d..efd90a14 100644 --- a/persistence/sql/queries/securities.sql +++ b/persistence/sql/queries/securities.sql @@ -14,11 +14,21 @@ FROM ORDER BY id; +-- name: ListSecuritiesByIDs :many +SELECT + * +FROM + securities +WHERE + id IN (sqlc.slice ('ids')) +ORDER BY + id; + -- name: CreateSecurity :one INSERT INTO - securities (id, display_name) + securities (id, display_name, quote_provider) VALUES - (?, ?) RETURNING *; + (?, ?, ?) RETURNING *; -- name: UpdateSecurity :one UPDATE securities @@ -36,13 +46,21 @@ WHERE -- name: UpsertListedSecurity :one INSERT INTO - listed_securities (security_id, ticker, currency) + listed_securities ( + security_id, + ticker, + currency, + latest_quote, + latest_quote_timestamp + ) VALUES - (?, ?, ?) ON CONFLICT (security_id, ticker) DO + (?, ?, ?, ?, ?) ON CONFLICT (security_id, ticker) DO UPDATE SET ticker = excluded.ticker, - currency = excluded.currency RETURNING *; + currency = excluded.currency, + latest_quote = excluded.latest_quote, + latest_quote_timestamp = excluded.latest_quote_timestamp RETURNING *; -- name: ListListedSecuritiesBySecurityID :many SELECT diff --git a/portfolio/accounts/type.go b/portfolio/accounts/type.go new file mode 100644 index 00000000..6731ebd7 --- /dev/null +++ b/portfolio/accounts/type.go @@ -0,0 +1,41 @@ +package accounts + +import ( + "github.com/oxisto/money-gopher/internal/enum" +) + +//go:generate stringer -linecomment -type=AccountType -output=type_string.go + +// AccountType is the type of an account. +type AccountType int + +const ( + // AccountTypeBrokerage represents a brokerage account. + AccountTypeBrokerage AccountType = iota + 1 // BROKERAGE + // AccountTypeBank represents a bank account. + AccountTypeBank // BANK + // AccountTypeLoan represents a loan account. + AccountTypeLoan // LOAN +) + +// Get implements [flag.Getter]. +func (t *AccountType) Get() any { + return t +} + +// Set implements [flag.Value]. +func (t *AccountType) Set(v string) error { + return enum.Set(t, v, _AccountType_name, _AccountType_index[:]) +} + +// MarshalJSON marshals the account type to JSON using the string +// representation. +func (t AccountType) MarshalJSON() ([]byte, error) { + return enum.MarshalJSON(t) +} + +// UnmarshalJSON unmarshals the account type from JSON. It expects a string +// representation. +func (t *AccountType) UnmarshalJSON(data []byte) error { + return enum.UnmarshalJSON(t, data) +} diff --git a/portfolio/accounts/type_string.go b/portfolio/accounts/type_string.go new file mode 100644 index 00000000..19de771f --- /dev/null +++ b/portfolio/accounts/type_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -linecomment -type=AccountType -output=type_string.go"; DO NOT EDIT. + +package accounts + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AccountTypeBrokerage-1] + _ = x[AccountTypeBank-2] + _ = x[AccountTypeLoan-3] +} + +const _AccountType_name = "BROKERAGEBANKLOAN" + +var _AccountType_index = [...]uint8{0, 9, 13, 17} + +func (i AccountType) String() string { + i -= 1 + if i < 0 || i >= AccountType(len(_AccountType_index)-1) { + return "AccountType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _AccountType_name[_AccountType_index[i]:_AccountType_index[i+1]] +} diff --git a/portfolio/events/type.go b/portfolio/events/type.go new file mode 100644 index 00000000..f2c60e1a --- /dev/null +++ b/portfolio/events/type.go @@ -0,0 +1,49 @@ +package events + +import "github.com/oxisto/money-gopher/internal/enum" + +//go:generate stringer -linecomment -type=PortfolioEventType -output=type_string.go + +// PortfolioEventType is the type of a portfolio event. +type PortfolioEventType int + +const ( + // PortfolioEventTypeBuy represents a buy event. + PortfolioEventTypeBuy PortfolioEventType = iota + 1 // BUY + // PortfolioEventTypeSell represents a sell event. + PortfolioEventTypeSell // SELL + // PortfolioEventTypeDividend represents a dividend event. + PortfolioEventTypeDividend // DIVIDEND + // PortfolioEventTypeTax represents the inbound delivery of a security or cash from another account. + PortfolioEventTypeDeliveryInbound // DELIVERY_INBOUND + // PortfolioEventTypeTax represents the outbound delivery of a security or cash to another account. + PortfolioEventTypeDeliveryOutbound // DELIVERY_OUTBOUND + // PortfolioEventTypeDepositCash represents a deposit of cash. + PortfolioEventTypeDepositCash // DEPOSIT_CASH + // PortfolioEventTypeWithdrawCash represents a withdrawal of cash. + PortfolioEventTypeWithdrawCash // WITHDRAW_CASH + // PortfolioEventTypeUnknown represents an unknown event type. + PortfolioEventTypeUnknown // UNKNOWN +) + +// Get implements [flag.Getter]. +func (t *PortfolioEventType) Get() any { + return t +} + +// Set implements [flag.Value]. +func (t *PortfolioEventType) Set(v string) error { + return enum.Set(t, v, _PortfolioEventType_name, _PortfolioEventType_index[:]) +} + +// MarshalJSON marshals the account type to JSON using the string +// representation. +func (t PortfolioEventType) MarshalJSON() ([]byte, error) { + return enum.MarshalJSON(t) +} + +// UnmarshalJSON unmarshals the account type from JSON. It expects a string +// representation. +func (t *PortfolioEventType) UnmarshalJSON(data []byte) error { + return enum.UnmarshalJSON(t, data) +} diff --git a/portfolio/events/type_string.go b/portfolio/events/type_string.go new file mode 100644 index 00000000..a11f1ea4 --- /dev/null +++ b/portfolio/events/type_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -linecomment -type=PortfolioEventType -output=type_string.go"; DO NOT EDIT. + +package events + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[PortfolioEventTypeBuy-1] + _ = x[PortfolioEventTypeSell-2] + _ = x[PortfolioEventTypeDividend-3] + _ = x[PortfolioEventTypeDeliveryInbound-4] + _ = x[PortfolioEventTypeDeliveryOutbound-5] + _ = x[PortfolioEventTypeDepositCash-6] + _ = x[PortfolioEventTypeWithdrawCash-7] + _ = x[PortfolioEventTypeUnknown-8] +} + +const _PortfolioEventType_name = "BUYSELLDIVIDENDDELIVERY_INBOUNDDELIVERY_OUTBOUNDDEPOSIT_CASHWITHDRAW_CASHUNKNOWN" + +var _PortfolioEventType_index = [...]uint8{0, 3, 7, 15, 31, 48, 60, 73, 80} + +func (i PortfolioEventType) String() string { + i -= 1 + if i < 0 || i >= PortfolioEventType(len(_PortfolioEventType_index)-1) { + return "PortfolioEventType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _PortfolioEventType_name[_PortfolioEventType_index[i]:_PortfolioEventType_index[i+1]] +} diff --git a/securities/quote/quote.go b/securities/quote/quote.go new file mode 100644 index 00000000..22301d7c --- /dev/null +++ b/securities/quote/quote.go @@ -0,0 +1,147 @@ +// Copyright 2023 Christian Banse +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file is part of The Money Gopher. + +// package quote contains the logic to update quotes for securities. Its main +// way to interface is the [QuoteUpdater] interface. A default implementation +// for the interface can be created using [NewQuoteUpdater]. +package quote + +import ( + "context" + "log/slog" + "time" + + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" + "golang.org/x/sync/errgroup" + + "github.com/lmittmann/tint" +) + +// QuoteProvider is an interface that retrieves quotes for a [ListedSecurity]. They +// can either be historical quotes or the latest quote. +type QuoteUpdater interface { + UpdateQuotes(ctx context.Context, IDs []string) (updated []*persistence.ListedSecurity, err error) +} + +// qu is the internal default implementation of the [QuoteUpdater] interface. +type qu struct { + db *persistence.DB +} + +// NewQuoteUpdater creates a new instance of the [QuoteUpdater] interface. +func NewQuoteUpdater(db *persistence.DB) QuoteUpdater { + return &qu{ + db: db, + } +} + +// UpdateQuotes triggers an update of the quotes for the given securities' IDs. +// It will return the updated listed securities. +func (qu *qu) UpdateQuotes(ctx context.Context, secIDs []string) (updated []*persistence.ListedSecurity, err error) { + var ( + secs []*persistence.Security + listed []*persistence.ListedSecurity + qp QuoteProvider + ok bool + ) + + // Fetch all securities if no IDs are given + if len(secIDs) == 0 { + secs, err = qu.db.ListSecurities(ctx) + } else { + secs, err = qu.db.ListSecuritiesByIDs(ctx, secIDs) + } + if err != nil { + return nil, err + } + + for _, sec := range secs { + if sec.QuoteProvider == nil { + slog.Warn("No quote provider configured for security", "security", sec.ID) + return + } + + qp, ok = providers[*sec.QuoteProvider] + if !ok { + slog.Error("Quote provider not found", "provider", *sec.QuoteProvider) + return + } + + listed, err = sec.ListedAs(ctx, qu.db) + if err != nil { + return nil, err + } + + // Trigger update from quote provider in separate go-routine + g, _ := errgroup.WithContext(ctx) + for _, ls := range listed { + ls := ls + g.Go(func() error { + slog.Debug("Triggering quote update", "security", ls, "provider", sec.QuoteProvider) + + err = qu.updateQuote(qp, ls) + if err != nil { + return err + } + + updated = append(updated, ls) + + return nil + }) + } + + err = g.Wait() + if err != nil { + slog.Error("An error occurred during quote update", tint.Err(err)) + return nil, err + } + } + + return +} + +// updateQuote updates the quote for the given [ListedSecurity] using the given [QuoteProvider]. +func (qu *qu) updateQuote(qp QuoteProvider, ls *persistence.ListedSecurity) (err error) { + var ( + quote *currency.Currency + t time.Time + ctx context.Context + cancel context.CancelFunc + ) + + ctx, cancel = context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + quote, t, err = qp.LatestQuote(ctx, ls) + if err != nil { + return err + } + + ls.LatestQuote = quote + ls.LatestQuoteTimestamp = &t + + _, err = qu.db.UpsertListedSecurity(ctx, persistence.UpsertListedSecurityParams{ + SecurityID: ls.SecurityID, + Ticker: ls.Ticker, + Currency: ls.Currency, + }) + if err != nil { + return err + } + + return +} diff --git a/service/securities/quote_provider.go b/securities/quote/quote_provider.go similarity index 84% rename from service/securities/quote_provider.go rename to securities/quote/quote_provider.go index 0cc3d9a1..2cd9141c 100644 --- a/service/securities/quote_provider.go +++ b/securities/quote/quote_provider.go @@ -14,13 +14,14 @@ // // This file is part of The Money Gopher. -package securities +package quote import ( "context" "time" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" ) // providers contains a map of all quote providers @@ -39,5 +40,5 @@ func RegisterQuoteProvider(name string, qp QuoteProvider) { // QuoteProvider is an interface that retrieves quotes for a [ListedSecurity]. They // can either be historical quotes or the latest quote. type QuoteProvider interface { - LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) (quote *portfoliov1.Currency, t time.Time, err error) + LatestQuote(ctx context.Context, ls *persistence.ListedSecurity) (quote *currency.Currency, t time.Time, err error) } diff --git a/service/securities/quote_provider_ing.go b/securities/quote/quote_provider_ing.go similarity index 78% rename from service/securities/quote_provider_ing.go rename to securities/quote/quote_provider_ing.go index a5dbefd8..764eb66f 100644 --- a/service/securities/quote_provider_ing.go +++ b/securities/quote/quote_provider_ing.go @@ -14,7 +14,7 @@ // // This file is part of The Money Gopher. -package securities +package quote import ( "context" @@ -23,7 +23,8 @@ import ( "net/http" "time" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" ) const QuoteProviderING = "ing" @@ -45,13 +46,13 @@ type header struct { WKN string `json:"wkn"` } -func (ing *ing) LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) (quote *portfoliov1.Currency, t time.Time, err error) { +func (ing *ing) LatestQuote(ctx context.Context, ls *persistence.ListedSecurity) (quote *currency.Currency, t time.Time, err error) { var ( res *http.Response h header ) - res, err = ing.Get(fmt.Sprintf("https://component-api.wertpapiere.ing.de/api/v1/components/instrumentheader/%s", ls.SecurityId)) + res, err = ing.Get(fmt.Sprintf("https://component-api.wertpapiere.ing.de/api/v1/components/instrumentheader/%s", ls.SecurityID)) if err != nil { return nil, t, fmt.Errorf("could not fetch quote: %w", err) } @@ -62,8 +63,8 @@ func (ing *ing) LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) } if h.HasBidAsk { - return portfoliov1.Value(int32(h.Bid * 100)), h.BidDate, nil + return currency.Value(int32(h.Bid * 100)), h.BidDate, nil } else { - return portfoliov1.Value(int32(h.Price * 100)), h.PriceChangedDate, nil + return currency.Value(int32(h.Price * 100)), h.PriceChangedDate, nil } } diff --git a/service/securities/quote_provider_ing_test.go b/securities/quote/quote_provider_ing_test.go similarity index 80% rename from service/securities/quote_provider_ing_test.go rename to securities/quote/quote_provider_ing_test.go index f7cf8393..f9a337be 100644 --- a/service/securities/quote_provider_ing_test.go +++ b/securities/quote/quote_provider_ing_test.go @@ -14,7 +14,7 @@ // // This file is part of The Money Gopher. -package securities +package quote import ( "context" @@ -25,8 +25,8 @@ import ( "testing" "time" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "google.golang.org/protobuf/testing/protocmp" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" "github.com/oxisto/assert" ) @@ -37,13 +37,13 @@ func Test_ing_LatestQuote(t *testing.T) { } type args struct { ctx context.Context - ls *portfoliov1.ListedSecurity + ls *persistence.ListedSecurity } tests := []struct { name string fields fields args args - wantQuote *portfoliov1.Currency + wantQuote *currency.Currency wantTime time.Time wantErr assert.Want[error] }{ @@ -56,8 +56,8 @@ func Test_ing_LatestQuote(t *testing.T) { }, args: args{ ctx: context.TODO(), - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, @@ -76,8 +76,8 @@ func Test_ing_LatestQuote(t *testing.T) { }), }, args: args{ - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, @@ -96,13 +96,13 @@ func Test_ing_LatestQuote(t *testing.T) { }), }, args: args{ - ls: &portfoliov1.ListedSecurity{ - SecurityId: "DE0000000001", + ls: &persistence.ListedSecurity{ + SecurityID: "DE0000000001", Ticker: "", Currency: "EUR", }, }, - wantQuote: portfoliov1.Value(10000), + wantQuote: currency.Value(10000), wantTime: time.Date(2023, 05, 04, 20, 0, 0, 0, time.UTC), wantErr: func(t *testing.T, err error) bool { return true }, }, @@ -113,9 +113,9 @@ func Test_ing_LatestQuote(t *testing.T) { Client: tt.fields.Client, } gotQuote, gotTime, err := ing.LatestQuote(tt.args.ctx, tt.args.ls) - assert.Equals(t, true, tt.wantErr(t, err), protocmp.Transform()) - assert.Equals(t, tt.wantQuote, gotQuote, protocmp.Transform()) - assert.Equals(t, tt.wantTime.UTC(), gotTime.UTC(), protocmp.Transform()) + assert.Equals(t, true, tt.wantErr(t, err)) + assert.Equals(t, tt.wantQuote, gotQuote) + assert.Equals(t, tt.wantTime.UTC(), gotTime.UTC()) }) } } diff --git a/service/securities/quote_provider_yf.go b/securities/quote/quote_provider_yf.go similarity index 83% rename from service/securities/quote_provider_yf.go rename to securities/quote/quote_provider_yf.go index 7f5f4dc1..fdee4600 100644 --- a/service/securities/quote_provider_yf.go +++ b/securities/quote/quote_provider_yf.go @@ -14,7 +14,7 @@ // // This file is part of The Money Gopher. -package securities +package quote import ( "context" @@ -24,7 +24,8 @@ import ( "net/http" "time" - portfoliov1 "github.com/oxisto/money-gopher/gen" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" ) var ErrEmptyResult = errors.New("empty result") @@ -46,7 +47,7 @@ type chart struct { } `json:"chart"` } -func (yf *yf) LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) (quote *portfoliov1.Currency, t time.Time, err error) { +func (yf *yf) LatestQuote(ctx context.Context, ls *persistence.ListedSecurity) (quote *currency.Currency, t time.Time, err error) { var ( res *http.Response ch chart @@ -66,6 +67,6 @@ func (yf *yf) LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) ( return nil, t, ErrEmptyResult } - return portfoliov1.Value(int32(ch.Chart.Results[0].Meta.RegularMarketPrice * 100)), + return currency.Value(int32(ch.Chart.Results[0].Meta.RegularMarketPrice * 100)), time.Unix(ch.Chart.Results[0].Meta.RegularMarketTime, 0), nil } diff --git a/service/securities/quote_provider_yf_test.go b/securities/quote/quote_provider_yf_test.go similarity index 82% rename from service/securities/quote_provider_yf_test.go rename to securities/quote/quote_provider_yf_test.go index 63d69709..cf31f527 100644 --- a/service/securities/quote_provider_yf_test.go +++ b/securities/quote/quote_provider_yf_test.go @@ -14,7 +14,7 @@ // // This file is part of The Money Gopher. -package securities +package quote import ( "context" @@ -25,8 +25,8 @@ import ( "testing" "time" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "google.golang.org/protobuf/testing/protocmp" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/persistence" "github.com/oxisto/assert" ) @@ -51,13 +51,13 @@ func Test_yf_LatestQuote(t *testing.T) { } type args struct { ctx context.Context - ls *portfoliov1.ListedSecurity + ls *persistence.ListedSecurity } tests := []struct { name string fields fields args args - wantQuote *portfoliov1.Currency + wantQuote *currency.Currency wantTime time.Time wantErr assert.Want[error] }{ @@ -70,8 +70,8 @@ func Test_yf_LatestQuote(t *testing.T) { }, args: args{ ctx: context.TODO(), - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, @@ -90,8 +90,8 @@ func Test_yf_LatestQuote(t *testing.T) { }), }, args: args{ - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, @@ -110,8 +110,8 @@ func Test_yf_LatestQuote(t *testing.T) { }), }, args: args{ - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, @@ -130,13 +130,13 @@ func Test_yf_LatestQuote(t *testing.T) { }), }, args: args{ - ls: &portfoliov1.ListedSecurity{ - SecurityId: "My Security", + ls: &persistence.ListedSecurity{ + SecurityID: "My Security", Ticker: "TICK", Currency: "USD", }, }, - wantQuote: portfoliov1.Value(10000), + wantQuote: currency.Value(10000), wantTime: time.Date(2023, 05, 04, 20, 0, 0, 0, time.UTC), wantErr: func(t *testing.T, err error) bool { return true }, }, @@ -147,9 +147,9 @@ func Test_yf_LatestQuote(t *testing.T) { Client: tt.fields.Client, } gotQuote, gotTime, err := yf.LatestQuote(tt.args.ctx, tt.args.ls) - assert.Equals(t, true, tt.wantErr(t, err), protocmp.Transform()) - assert.Equals(t, tt.wantQuote, gotQuote, protocmp.Transform()) - assert.Equals(t, tt.wantTime.UTC(), gotTime.UTC(), protocmp.Transform()) + assert.Equals(t, true, tt.wantErr(t, err)) + assert.Equals(t, tt.wantQuote, gotQuote) + assert.Equals(t, tt.wantTime.UTC(), gotTime.UTC()) }) } } diff --git a/securities/quote/quote_test.go b/securities/quote/quote_test.go new file mode 100644 index 00000000..f90b897d --- /dev/null +++ b/securities/quote/quote_test.go @@ -0,0 +1,68 @@ +package quote + +import ( + "context" + "testing" + + "github.com/oxisto/assert" + moneygopher "github.com/oxisto/money-gopher" + "github.com/oxisto/money-gopher/currency" + "github.com/oxisto/money-gopher/internal" + "github.com/oxisto/money-gopher/internal/testing/quotetest" + "github.com/oxisto/money-gopher/persistence" +) + +func Test_qu_updateQuote(t *testing.T) { + type fields struct { + db *persistence.DB + } + type args struct { + qp QuoteProvider + ls *persistence.ListedSecurity + } + tests := []struct { + name string + fields fields + args args + want assert.Want[*persistence.ListedSecurity] + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + db: internal.NewTestDB(t, func(db *persistence.DB) { + _, err := db.CreateSecurity(context.Background(), persistence.CreateSecurityParams{ + ID: "My Security", + QuoteProvider: moneygopher.Ref(quotetest.QuoteProviderStatic), + }) + assert.NoError(t, err) + _, err = db.UpsertListedSecurity(context.Background(), persistence.UpsertListedSecurityParams{ + SecurityID: "My Security", + Ticker: "SEC", + Currency: "EUR", + }) + assert.NoError(t, err) + }), + }, + args: args{ + qp: quotetest.NewStaticQuoteProvider(currency.Value(100)), + ls: &persistence.ListedSecurity{SecurityID: "My Security", Ticker: "SEC", Currency: "EUR"}, + }, + want: func(t *testing.T, ls *persistence.ListedSecurity) bool { + return assert.Equals(t, 100, ls.LatestQuote.Amount) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svc := &qu{ + db: tt.fields.db, + } + if err := svc.updateQuote(tt.args.qp, tt.args.ls); (err != nil) != tt.wantErr { + t.Errorf("updateQuote() error = %v, wantErr %v", err, tt.wantErr) + } + + tt.want(t, tt.args.ls) + }) + } +} diff --git a/securities/securities.go b/securities/securities.go new file mode 100644 index 00000000..48b5a37d --- /dev/null +++ b/securities/securities.go @@ -0,0 +1,13 @@ +package securities + +import ( + "context" + + "github.com/oxisto/money-gopher/persistence" +) + +// SecuritiesLister is the interface for listing securities. +type SecuritiesLister interface { + ListSecurities(ctx context.Context) ([]*persistence.Security, error) + ListSecuritiesByIDs(ctx context.Context, ids []string) ([]*persistence.Security, error) +} diff --git a/server/commands/init.go b/server/commands/init.go index cafcac43..5239dd86 100644 --- a/server/commands/init.go +++ b/server/commands/init.go @@ -83,11 +83,11 @@ func RunServer(ctx context.Context, cmd *cli.Command) error { slog.SetDefault(logger) slog.Info("Welcome to the Money Gopher", "money", "🤑") - pdb, q, err := persistence.OpenDB(persistence.Options{}) + db, err := persistence.OpenDB(persistence.Options{}) if err != nil { slog.Error("Error while opening database", tint.Err(err)) return err } - return server.StartServer(pdb, q, opts) + return server.StartServer(db, opts) } diff --git a/server/interceptors.go b/server/interceptors.go deleted file mode 100644 index 5d61f777..00000000 --- a/server/interceptors.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2024 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package server - -import ( - "context" - "errors" - "log/slog" - "strings" - - "connectrpc.com/connect" - "github.com/MicahParks/keyfunc/v3" - "github.com/golang-jwt/jwt/v5" - "github.com/lmittmann/tint" -) - -// NewSimpleLoggingInterceptor returns a new simple logging interceptor. -func NewSimpleLoggingInterceptor() connect.UnaryInterceptorFunc { - interceptor := func(next connect.UnaryFunc) connect.UnaryFunc { - return connect.UnaryFunc(func( - ctx context.Context, - req connect.AnyRequest, - ) (connect.AnyResponse, error) { - slog.Debug("Handling RPC Request", - slog.Group("req", - "procedure", req.Spec().Procedure, - "httpmethod", req.HTTPMethod(), - )) - return next(ctx, req) - }) - } - - return connect.UnaryInterceptorFunc(interceptor) -} - -// NewAuthInterceptor returns a new auth interceptor. -func NewAuthInterceptor() connect.UnaryInterceptorFunc { - interceptor := func(next connect.UnaryFunc) connect.UnaryFunc { - k, err := keyfunc.NewDefault([]string{"http://localhost:8000/certs"}) - if err != nil { - slog.Error("Error while setting up JWKS", tint.Err(err)) - } - - return connect.UnaryFunc(func( - ctx context.Context, - req connect.AnyRequest, - ) (connect.AnyResponse, error) { - var ( - claims jwt.RegisteredClaims - auth string - token string - err error - ok bool - ) - auth = req.Header().Get("Authorization") - if auth == "" { - return nil, connect.NewError( - connect.CodeUnauthenticated, - errors.New("no token provided"), - ) - } - - token, ok = strings.CutPrefix(auth, "Bearer ") - if !ok { - return nil, connect.NewError( - connect.CodeUnauthenticated, - errors.New("no token provided"), - ) - } - - _, err = jwt.ParseWithClaims(token, &claims, k.Keyfunc) - if err != nil { - return nil, connect.NewError( - connect.CodeUnauthenticated, - err, - ) - } - - ctx = context.WithValue(ctx, "claims", claims) - return next(ctx, req) - }) - } - return connect.UnaryInterceptorFunc(interceptor) -} diff --git a/server/server.go b/server/server.go index 9795d73a..18d87582 100644 --- a/server/server.go +++ b/server/server.go @@ -21,13 +21,14 @@ import ( "log/slog" "net/http" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/99designs/gqlgen/graphql/playground" + "github.com/oxisto/money-gopher/graph" "github.com/oxisto/money-gopher/persistence" - "github.com/oxisto/money-gopher/service/portfolio" - "github.com/oxisto/money-gopher/service/securities" + "github.com/oxisto/money-gopher/securities/quote" - "connectrpc.com/connect" - "connectrpc.com/vanguard" "github.com/lmittmann/tint" oauth2 "github.com/oxisto/oauth2go" "github.com/oxisto/oauth2go/login" @@ -47,10 +48,9 @@ type Options struct { } // StartServer starts the server. -func StartServer(pdb *persistence.DB, q *persistence.Queries, opts Options) (err error) { +func StartServer(pdb *persistence.DB, opts Options) (err error) { var ( - authSrv *oauth2.AuthorizationServer - transcoder *vanguard.Transcoder + authSrv *oauth2.AuthorizationServer ) authSrv = oauth2.NewServer( @@ -68,37 +68,12 @@ func StartServer(pdb *persistence.DB, q *persistence.Queries, opts Options) (err ) go authSrv.ListenAndServe() - interceptors := connect.WithInterceptors( - NewSimpleLoggingInterceptor(), - NewAuthInterceptor(), - ) - - portfolioService := vanguard.NewService( - portfoliov1connect.NewPortfolioServiceHandler(portfolio.NewService( - portfolio.Options{ - DB: pdb, - SecuritiesClient: portfoliov1connect.NewSecuritiesServiceClient(http.DefaultClient, portfolio.DefaultSecuritiesServiceURL), - }, - ), interceptors)) - securitiesService := vanguard.NewService( - portfoliov1connect.NewSecuritiesServiceHandler(securities.NewService(pdb), interceptors), - ) - - transcoder, err = vanguard.NewTranscoder([]*vanguard.Service{ - portfolioService, - securitiesService, - }, vanguard.WithCodec(func(tr vanguard.TypeResolver) vanguard.Codec { - codec := vanguard.NewJSONCodec(tr) - codec.MarshalOptions.EmitDefaultValues = true - return codec - })) - if err != nil { - slog.Error("transcoder failed", tint.Err(err)) - return err - } + // Create a quote updater + qu := quote.NewQuoteUpdater(pdb) + // Configure serve mux mux := http.NewServeMux() - mux.Handle("/", transcoder) + ConfigureGraphQL(mux, pdb, qu) err = http.ListenAndServe( ":8080", @@ -108,3 +83,26 @@ func StartServer(pdb *persistence.DB, q *persistence.Queries, opts Options) (err slog.Error("listen failed", tint.Err(err)) return err } + +// ConfigureGraphQL configures the GraphQL server for a [http.ServeMux]. +func ConfigureGraphQL( + mux *http.ServeMux, + db *persistence.DB, + qu quote.QuoteUpdater, +) (err error) { + srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{ + DB: db, + QuoteUpdater: qu, + }})) + + srv.AddTransport(transport.Options{}) + srv.AddTransport(transport.GET{}) + srv.AddTransport(transport.POST{}) + + srv.Use(extension.Introspection{}) + + mux.Handle("/graphql", playground.Handler("GraphQL playground", "/graphql/query")) + mux.Handle("/graphql/query", srv) + + return err +} diff --git a/service/internal/crud/crud_requests.go b/service/internal/crud/crud_requests.go deleted file mode 100644 index f506e83e..00000000 --- a/service/internal/crud/crud_requests.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -// package crud contains helpers to handle CRUD (Create, Read, Update and -// Delete) requests that work on [persistence.StorageOperations] in a common -// way. -package crud - -import ( - "fmt" - "log/slog" - "strings" - - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "google.golang.org/protobuf/types/known/emptypb" -) - -func Create[T any, S persistence.StorageObject](obj S, op persistence.StorageOperations[S], convert func(obj S) *T) (res *connect.Response[T], err error) { - var typ = fmt.Sprintf("%T", obj) - - _, typ, _ = strings.Cut(typ, ".") - typ = strings.ToLower(typ) - - slog.Info( - fmt.Sprintf("Creating %s", typ), - typ, obj, - ) - - // TODO(oxisto): We probably want to have a pure create instead of replace here - err = op.Replace(obj) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(convert(obj)) - - return -} - -func List[T any, S persistence.StorageObject](op persistence.StorageOperations[S], setter func(res *connect.Response[T], list []S) error, args ...any) (res *connect.Response[T], err error) { - obj, err := op.List(args...) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(new(T)) - err = setter(res, obj) - - return -} - -func Get[T any, S persistence.StorageObject](key any, op persistence.StorageOperations[S], convert func(obj S) *T) (res *connect.Response[T], err error) { - obj, err := op.Get(key) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(convert(obj)) - - return -} - -func Update[T any, S persistence.StorageObject](key any, in S, paths []string, op persistence.StorageOperations[S], convert func(obj S) *T) (res *connect.Response[T], err error) { - out, err := op.Update(key, in, paths) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(convert(out)) - - return -} - -func Delete[S persistence.StorageObject](key any, op persistence.StorageOperations[S]) (res *connect.Response[emptypb.Empty], err error) { - err = op.Delete(key) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(&emptypb.Empty{}) - - return -} diff --git a/service/internal/crud/crud_requests_test.go b/service/internal/crud/crud_requests_test.go deleted file mode 100644 index 5d7c827e..00000000 --- a/service/internal/crud/crud_requests_test.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package crud - -import ( - "errors" - "reflect" - "testing" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "google.golang.org/protobuf/types/known/emptypb" -) - -func TestCreate(t *testing.T) { - type args struct { - obj *portfoliov1.Portfolio - op persistence.StorageOperations[*portfoliov1.Portfolio] - convert func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio - } - tests := []struct { - name string - args args - wantRes *connect.Response[portfoliov1.Portfolio] - wantErr bool - }{ - { - name: "error", - args: args{ - op: internal.ErrOps[*portfoliov1.Portfolio](errors.New("some-error")), - convert: func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - return obj - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := Create(tt.args.obj, tt.args.op, tt.args.convert) - if (err != nil) != tt.wantErr { - t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) { - t.Errorf("Create() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestList(t *testing.T) { - type args struct { - op persistence.StorageOperations[*portfoliov1.Portfolio] - setter func(res *connect.Response[portfoliov1.ListPortfoliosResponse], list []*portfoliov1.Portfolio) error - args []any - } - tests := []struct { - name string - args args - wantRes *connect.Response[portfoliov1.ListPortfoliosResponse] - wantErr bool - }{ - { - name: "error", - args: args{ - op: internal.ErrOps[*portfoliov1.Portfolio](errors.New("some-error")), - setter: func(res *connect.Response[portfoliov1.ListPortfoliosResponse], list []*portfoliov1.Portfolio) error { - res.Msg.Portfolios = list - return nil - }, - args: []any{"some-key"}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := List(tt.args.op, tt.args.setter, tt.args.args...) - if (err != nil) != tt.wantErr { - t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !assert.Equals(t, tt.wantRes, gotRes) { - t.Errorf("List() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestGet(t *testing.T) { - type args struct { - key any - op persistence.StorageOperations[*portfoliov1.Portfolio] - convert func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio - } - tests := []struct { - name string - args args - wantRes *connect.Response[portfoliov1.Portfolio] - wantErr bool - }{ - { - name: "error", - args: args{ - key: "some-key", - op: internal.ErrOps[*portfoliov1.Portfolio](errors.New("some-error")), - convert: func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - return obj - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := Get(tt.args.key, tt.args.op, tt.args.convert) - if (err != nil) != tt.wantErr { - t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) { - t.Errorf("Get() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestUpdate(t *testing.T) { - type args struct { - key any - in *portfoliov1.Portfolio - paths []string - op persistence.StorageOperations[*portfoliov1.Portfolio] - convert func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio - } - tests := []struct { - name string - args args - wantRes *connect.Response[portfoliov1.Portfolio] - wantErr bool - }{ - { - name: "error", - args: args{ - key: "some-key", - op: internal.ErrOps[*portfoliov1.Portfolio](errors.New("some-error")), - convert: func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - return obj - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := Update(tt.args.key, tt.args.in, tt.args.paths, tt.args.op, tt.args.convert) - if (err != nil) != tt.wantErr { - t.Errorf("Update() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) { - t.Errorf("Update() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestDelete(t *testing.T) { - type args struct { - key any - op persistence.StorageOperations[*portfoliov1.Portfolio] - } - tests := []struct { - name string - args args - wantRes *connect.Response[emptypb.Empty] - wantErr bool - }{ - { - name: "error", - args: args{ - key: "some-key", - op: internal.ErrOps[*portfoliov1.Portfolio](errors.New("some-error")), - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := Delete(tt.args.key, tt.args.op) - if (err != nil) != tt.wantErr { - t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) { - t.Errorf("Delete() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} diff --git a/service/portfolio/account.go b/service/portfolio/account.go deleted file mode 100644 index 8c2b206a..00000000 --- a/service/portfolio/account.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - - "connectrpc.com/connect" - "github.com/oxisto/money-gopher/service/internal/crud" - "google.golang.org/protobuf/types/known/emptypb" -) - -var bankAccountSetter = func(obj *portfoliov1.BankAccount) *portfoliov1.BankAccount { - return obj -} - -func (svc *service) CreateBankAccount(ctx context.Context, req *connect.Request[portfoliov1.CreateBankAccountRequest]) (res *connect.Response[portfoliov1.BankAccount], err error) { - return crud.Create( - req.Msg.BankAccount, - svc.bankAccounts, - bankAccountSetter, - ) -} - -func (svc *service) UpdateBankAccount(ctx context.Context, req *connect.Request[portfoliov1.UpdateBankAccountRequest]) (res *connect.Response[portfoliov1.BankAccount], err error) { - return crud.Update( - req.Msg.Account.Id, - req.Msg.Account, - req.Msg.UpdateMask.Paths, - svc.bankAccounts, - func(obj *portfoliov1.BankAccount) *portfoliov1.BankAccount { - return obj - }, - ) -} - -func (svc *service) DeleteBankAccount(ctx context.Context, req *connect.Request[portfoliov1.DeleteBankAccountRequest]) (res *connect.Response[emptypb.Empty], err error) { - return crud.Delete(req.Msg.Id, svc.bankAccounts) -} diff --git a/service/portfolio/account_test.go b/service/portfolio/account_test.go deleted file mode 100644 index 3000af35..00000000 --- a/service/portfolio/account_test.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - "testing" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "google.golang.org/protobuf/types/known/fieldmaskpb" -) - -func Test_service_CreateBankAccount(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - bankAccounts persistence.StorageOperations[*portfoliov1.BankAccount] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.CreateBankAccountRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.BankAccount]] - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: internal.NewTestDBOps[*portfoliov1.Portfolio](t), - bankAccounts: internal.NewTestDBOps[*portfoliov1.BankAccount](t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.CreateBankAccountRequest{ - BankAccount: &portfoliov1.BankAccount{ - Id: "mybank-mycash", - DisplayName: "My Cash Account", - }, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.BankAccount]) bool { - return true && - assert.Equals(t, "mybank-mycash", r.Msg.Id) && - assert.Equals(t, "My Cash Account", r.Msg.DisplayName) - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.bankAccounts.List() - return assert.Equals(t, 1, len(list)) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - bankAccounts: tt.fields.bankAccounts, - securities: tt.fields.securities, - } - gotRes, err := svc.CreateBankAccount(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.CreateBankAccount() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - tt.wantSvc(t, svc) - }) - } -} - -func Test_service_UpdateBankAccount(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - bankAccounts persistence.StorageOperations[*portfoliov1.BankAccount] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.UpdateBankAccountRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.BankAccount]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - bankAccounts: myCash(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.UpdateBankAccountRequest{ - Account: &portfoliov1.BankAccount{ - Id: "mybank-mycash", - DisplayName: "My Cash", - }, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"display_name"}}, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.BankAccount]) bool { - return assert.Equals(t, "My Cash", r.Msg.DisplayName) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - bankAccounts: tt.fields.bankAccounts, - } - gotRes, err := svc.UpdateBankAccount(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.UpdateBankAccount() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_DeleteBankAccount(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - bankAccounts persistence.StorageOperations[*portfoliov1.BankAccount] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.DeleteBankAccountRequest] - } - tests := []struct { - name string - fields fields - args args - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - bankAccounts: myCash(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.DeleteBankAccountRequest{ - Id: "mybank-mycash", - }), - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.bankAccounts.List() - return assert.Equals(t, 0, len(list)) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - bankAccounts: tt.fields.bankAccounts, - } - _, err := svc.DeleteBankAccount(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.DeleteBankAccount() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantSvc(t, svc) - }) - } -} diff --git a/service/portfolio/portfolio.go b/service/portfolio/portfolio.go deleted file mode 100644 index 95ad373a..00000000 --- a/service/portfolio/portfolio.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/service/internal/crud" - - "connectrpc.com/connect" - "google.golang.org/protobuf/types/known/emptypb" -) - -var portfolioSetter = func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - return obj -} - -func (svc *service) CreatePortfolio(ctx context.Context, req *connect.Request[portfoliov1.CreatePortfolioRequest]) (res *connect.Response[portfoliov1.Portfolio], err error) { - return crud.Create( - req.Msg.Portfolio, - svc.portfolios, - portfolioSetter, - ) -} - -func (svc *service) ListPortfolios(ctx context.Context, req *connect.Request[portfoliov1.ListPortfoliosRequest]) (res *connect.Response[portfoliov1.ListPortfoliosResponse], err error) { - return crud.List( - svc.portfolios, - func( - res *connect.Response[portfoliov1.ListPortfoliosResponse], - list []*portfoliov1.Portfolio, - ) error { - res.Msg.Portfolios = list - - for _, p := range res.Msg.Portfolios { - p.Events, err = svc.events.List(p.Id) - if err != nil { - return err - } - } - - return nil - }, - ) -} - -func (svc *service) GetPortfolio(ctx context.Context, req *connect.Request[portfoliov1.GetPortfolioRequest]) (res *connect.Response[portfoliov1.Portfolio], err error) { - return crud.Get( - req.Msg.Id, - svc.portfolios, - func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - obj.Events, _ = svc.events.List(obj.Id) - - return obj - }, - ) -} - -func (svc *service) UpdatePortfolio(ctx context.Context, req *connect.Request[portfoliov1.UpdatePortfolioRequest]) (res *connect.Response[portfoliov1.Portfolio], err error) { - return crud.Update( - req.Msg.Portfolio.Id, - req.Msg.Portfolio, - req.Msg.UpdateMask.Paths, - svc.portfolios, - func(obj *portfoliov1.Portfolio) *portfoliov1.Portfolio { - return obj - }, - ) -} - -func (svc *service) DeletePortfolio(ctx context.Context, req *connect.Request[portfoliov1.DeletePortfolioRequest]) (res *connect.Response[emptypb.Empty], err error) { - return crud.Delete(req.Msg.Id, svc.portfolios) -} diff --git a/service/portfolio/portfolio_test.go b/service/portfolio/portfolio_test.go deleted file mode 100644 index 255b9aba..00000000 --- a/service/portfolio/portfolio_test.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - "testing" - "time" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "google.golang.org/protobuf/types/known/fieldmaskpb" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func myPortfolio(t *testing.T) persistence.StorageOperations[*portfoliov1.Portfolio] { - return internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Portfolio]) { - assert.NoError(t, ops.Replace(&portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Portfolio", - })) - rel := persistence.Relationship[*portfoliov1.PortfolioEvent](ops) - assert.NoError(t, rel.Replace(&portfoliov1.PortfolioEvent{ - Id: "buy", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - PortfolioId: "mybank-myportfolio", - SecurityId: "US0378331005", - Amount: 20, - Price: portfoliov1.Value(10708), - Fees: portfoliov1.Value(1025), - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), - })) - assert.NoError(t, rel.Replace(&portfoliov1.PortfolioEvent{ - Id: "sell", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, - PortfolioId: "mybank-myportfolio", - SecurityId: "US0378331005", - Amount: 10, - Price: portfoliov1.Value(14588), - Fees: portfoliov1.Value(855), - Time: timestamppb.New(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), - })) - }) -} - -func myCash(t *testing.T) persistence.StorageOperations[*portfoliov1.BankAccount] { - return internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.BankAccount]) { - assert.NoError(t, ops.Replace(&portfoliov1.BankAccount{ - Id: "mybank-mycash", - DisplayName: "My Cash", - })) - }) -} - -func zeroPositions(t *testing.T) persistence.StorageOperations[*portfoliov1.Portfolio] { - return internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Portfolio]) { - assert.NoError(t, ops.Replace(&portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Portfolio", - })) - rel := persistence.Relationship[*portfoliov1.PortfolioEvent](ops) - assert.NoError(t, rel.Replace(&portfoliov1.PortfolioEvent{ - Id: "buy", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - PortfolioId: "mybank-myportfolio", - SecurityId: "sec123", - Amount: 10, - Price: portfoliov1.Value(10000), - Fees: portfoliov1.Zero(), - Time: timestamppb.New(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)), - })) - assert.NoError(t, rel.Replace(&portfoliov1.PortfolioEvent{ - Id: "sell", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, - PortfolioId: "mybank-myportfolio", - SecurityId: "sec123", - Amount: 10, - Price: portfoliov1.Value(10000), - Fees: portfoliov1.Zero(), - Time: timestamppb.New(time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC)), - })) - }) -} - -func emptyPortfolio(t *testing.T) persistence.StorageOperations[*portfoliov1.Portfolio] { - return internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Portfolio]) { - assert.NoError(t, ops.Replace(&portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Portfolio", - })) - }) -} - -func Test_service_CreatePortfolio(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.CreatePortfolioRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.Portfolio]] - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: internal.NewTestDBOps[*portfoliov1.Portfolio](t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.CreatePortfolioRequest{ - Portfolio: &portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Portfolio", - }, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.Portfolio]) bool { - return true && - assert.Equals(t, "mybank-myportfolio", r.Msg.Id) && - assert.Equals(t, "My Portfolio", r.Msg.DisplayName) - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.portfolios.List() - return assert.Equals(t, 1, len(list)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.CreatePortfolio(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.CreatePortfolio() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - tt.wantSvc(t, svc) - }) - } -} - -func Test_service_ListPortfolios(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.ListPortfoliosRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.ListPortfoliosResponse]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.ListPortfoliosResponse]) bool { - return true && - assert.Equals(t, "mybank-myportfolio", r.Msg.Portfolios[0].Id) && - assert.Equals(t, "My Portfolio", r.Msg.Portfolios[0].DisplayName) && - assert.Equals(t, 2, len(r.Msg.Portfolios[0].Events)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.ListPortfolios(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.ListPortfolios() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_GetPortfolio(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.GetPortfolioRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.Portfolio]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.GetPortfolioRequest{ - Id: "mybank-myportfolio", - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.Portfolio]) bool { - return true && - assert.Equals(t, 2, len(r.Msg.Events)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.GetPortfolio(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.GetPortfolio() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_UpdatePortfolio(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.UpdatePortfolioRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.Portfolio]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.UpdatePortfolioRequest{ - Portfolio: &portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Second Portfolio", - }, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"display_name"}}, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.Portfolio]) bool { - return assert.Equals(t, "My Second Portfolio", r.Msg.DisplayName) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.UpdatePortfolio(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.UpdatePortfolio() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_DeletePortfolio(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - events persistence.StorageOperations[*portfoliov1.PortfolioEvent] - securities portfoliov1connect.SecuritiesServiceClient - UnimplementedPortfolioServiceHandler portfoliov1connect.UnimplementedPortfolioServiceHandler - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.DeletePortfolioRequest] - } - tests := []struct { - name string - fields fields - args args - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.DeletePortfolioRequest{ - Id: "mybank-myportfolio", - }), - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.portfolios.List() - return assert.Equals(t, 0, len(list)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: tt.fields.events, - securities: tt.fields.securities, - UnimplementedPortfolioServiceHandler: tt.fields.UnimplementedPortfolioServiceHandler, - } - _, err := svc.DeletePortfolio(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.DeletePortfolio() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantSvc(t, svc) - }) - } -} diff --git a/service/portfolio/service.go b/service/portfolio/service.go deleted file mode 100644 index eb837d83..00000000 --- a/service/portfolio/service.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -// package portfolio contains the code for the PortfolioService implementation. -package portfolio - -import ( - "net/http" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/persistence" -) - -const DefaultSecuritiesServiceURL = "http://localhost:8080" - -// service is the main struct fo the [PortfolioService] implementation. -type service struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - events persistence.StorageOperations[*portfoliov1.PortfolioEvent] - bankAccounts persistence.StorageOperations[*portfoliov1.BankAccount] - securities portfoliov1connect.SecuritiesServiceClient - - portfoliov1connect.UnimplementedPortfolioServiceHandler -} - -type Options struct { - SecuritiesClient portfoliov1connect.SecuritiesServiceClient - DB *persistence.DB -} - -func NewService(opts Options) portfoliov1connect.PortfolioServiceHandler { - var s service - - s.portfolios = persistence.Ops[*portfoliov1.Portfolio](opts.DB) - s.events = persistence.Relationship[*portfoliov1.PortfolioEvent](s.portfolios) - s.bankAccounts = persistence.Ops[*portfoliov1.BankAccount](opts.DB) - - s.securities = opts.SecuritiesClient - if s.securities == nil { - s.securities = portfoliov1connect.NewSecuritiesServiceClient(http.DefaultClient, DefaultSecuritiesServiceURL) - } - - // Add a simple starter portfolio - s.portfolios.Replace(&portfoliov1.Portfolio{ - Id: "mybank-myportfolio", - DisplayName: "My Portfolio", - BankAccountId: "mybank-mycash", - }) - - // Add its cash account - s.bankAccounts.Replace(&portfoliov1.BankAccount{ - Id: "mybank-mycash", - DisplayName: "My Cash Account", - }) - - return &s -} diff --git a/service/portfolio/service_test.go b/service/portfolio/service_test.go deleted file mode 100644 index b372883d..00000000 --- a/service/portfolio/service_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "testing" - - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/internal" - - "github.com/oxisto/assert" -) - -func TestNewService(t *testing.T) { - type args struct { - opts Options - } - tests := []struct { - name string - args args - want assert.Want[portfoliov1connect.PortfolioServiceHandler] - }{ - { - name: "with default client", - args: args{opts: Options{ - DB: internal.NewTestDB(t), - }}, - want: func(t *testing.T, psh portfoliov1connect.PortfolioServiceHandler) bool { - s := assert.Is[*service](t, psh) - return assert.NotNil(t, s.securities) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewService(tt.args.opts) - tt.want(t, got) - }) - } -} diff --git a/service/portfolio/snapshot.go b/service/portfolio/snapshot.go deleted file mode 100644 index 897b1876..00000000 --- a/service/portfolio/snapshot.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - "fmt" - - moneygopher "github.com/oxisto/money-gopher" - "github.com/oxisto/money-gopher/finance" - portfoliov1 "github.com/oxisto/money-gopher/gen" - - "connectrpc.com/connect" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func (svc *service) GetPortfolioSnapshot(ctx context.Context, req *connect.Request[portfoliov1.GetPortfolioSnapshotRequest]) (res *connect.Response[portfoliov1.PortfolioSnapshot], err error) { - var ( - snap *portfoliov1.PortfolioSnapshot - p portfoliov1.Portfolio - m map[string][]*portfoliov1.PortfolioEvent - names []string - secres *connect.Response[portfoliov1.ListSecuritiesResponse] - secmap map[string]*portfoliov1.Security - ) - - // Retrieve transactions - p.Events, err = svc.events.List(req.Msg.PortfolioId) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - // If no time is specified, we assume it to be now - if req.Msg.Time == nil { - req.Msg.Time = timestamppb.Now() - } - - // Set up the snapshot - snap = &portfoliov1.PortfolioSnapshot{ - Time: req.Msg.Time, - Positions: make(map[string]*portfoliov1.PortfolioPosition), - TotalPurchaseValue: portfoliov1.Zero(), - TotalMarketValue: portfoliov1.Zero(), - TotalProfitOrLoss: portfoliov1.Zero(), - Cash: portfoliov1.Zero(), - } - - // Record the first transaction time - if len(p.Events) > 0 { - snap.FirstTransactionTime = p.Events[0].Time - } - - // Retrieve the event map; a map of events indexed by their security ID - m = p.EventMap() - names = keys(m) - - // Retrieve market value of filtered securities - secres, err = svc.securities.ListSecurities( - context.Background(), - forwardAuth(connect.NewRequest(&portfoliov1.ListSecuritiesRequest{ - Filter: &portfoliov1.ListSecuritiesRequest_Filter{ - SecurityIds: names, - }, - }), req), - ) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, - fmt.Errorf("internal error while calling ListSecurities on securities service: %w", err), - ) - } - - // Make a map out of the securities list so we can access it easier - secmap = moneygopher.Map(secres.Msg.Securities, func(s *portfoliov1.Security) string { - return s.Id - }) - - // We need to look at the portfolio events up to the time of the snapshot - // and calculate the current positions. - for name, txs := range m { - txs = portfoliov1.EventsBefore(txs, snap.Time.AsTime()) - - c := finance.NewCalculation(txs) - - if name == "cash" { - // Add deposited/withdrawn cash directly - snap.Cash.PlusAssign(c.Cash) - continue - } - - if c.Amount == 0 { - continue - } - - // Also add cash that is part of a securities' transaction (e.g., sell/buy) - snap.Cash.PlusAssign(c.Cash) - - pos := &portfoliov1.PortfolioPosition{ - Security: secmap[name], - Amount: c.Amount, - PurchaseValue: c.NetValue(), - PurchasePrice: c.NetPrice(), - MarketValue: portfoliov1.Times(marketPrice(secmap, name, c.NetPrice()), c.Amount), - MarketPrice: marketPrice(secmap, name, c.NetPrice()), - } - - // Calculate loss and gains - pos.ProfitOrLoss = portfoliov1.Minus(pos.MarketValue, pos.PurchaseValue) - pos.Gains = float64(portfoliov1.Minus(pos.MarketValue, pos.PurchaseValue).Value) / float64(pos.PurchaseValue.Value) - - // Add to total value(s) - snap.TotalPurchaseValue.PlusAssign(pos.PurchaseValue) - snap.TotalMarketValue.PlusAssign(pos.MarketValue) - snap.TotalProfitOrLoss.PlusAssign(pos.ProfitOrLoss) - - // Store position in map - snap.Positions[name] = pos - } - - // Calculate total gains - snap.TotalGains = float64(portfoliov1.Minus(snap.TotalMarketValue, snap.TotalPurchaseValue).Value) / float64(snap.TotalPurchaseValue.Value) - - // Calculate total portfolio value - snap.TotalPortfolioValue = snap.TotalMarketValue.Plus(snap.Cash) - - return connect.NewResponse(snap), nil -} - -func marketPrice(secmap map[string]*portfoliov1.Security, name string, netPrice *portfoliov1.Currency) *portfoliov1.Currency { - ls := secmap[name].ListedOn - - if ls == nil || ls[0].LatestQuote == nil { - return netPrice - } else { - return ls[0].LatestQuote - } -} - -// TODO(oxisto): remove once maps.Keys is in the stdlib in Go 1.22 -func keys[M ~map[K]V, K comparable, V any](m M) (keys []K) { - keys = make([]K, 0, len(m)) - - for k := range m { - keys = append(keys, k) - } - - return keys -} - -// forwardAuth uses the authorization header of [authenticatedReq] to -// authenticate [req]. This is a little workaround, until we have proper -// service-to-service authentication. -func forwardAuth[T any, S any](req *connect.Request[T], authenticatedReq *connect.Request[S]) *connect.Request[T] { - req.Header().Set("Authorization", authenticatedReq.Header().Get("Authorization")) - return req -} diff --git a/service/portfolio/snapshot_test.go b/service/portfolio/snapshot_test.go deleted file mode 100644 index 037615ef..00000000 --- a/service/portfolio/snapshot_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - "io" - "testing" - "time" - - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "golang.org/x/text/currency" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/emptypb" - "google.golang.org/protobuf/types/known/timestamppb" -) - -var mockSecuritiesClientWithData = &mockSecuritiesClient{ - securities: []*portfoliov1.Security{ - { - Id: "US0378331005", - DisplayName: "Apple, Inc.", - ListedOn: []*portfoliov1.ListedSecurity{ - { - SecurityId: "US0378331005", - Ticker: "APC.F", - Currency: currency.EUR.String(), - LatestQuote: portfoliov1.Value(10000), - LatestQuoteTimestamp: timestamppb.Now(), - }, - }, - }, - }, -} - -func Test_service_GetPortfolioSnapshot(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - events persistence.StorageOperations[*portfoliov1.PortfolioEvent] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.GetPortfolioSnapshotRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.PortfolioSnapshot]] - wantErr bool - }{ - { - name: "happy path, now", - fields: fields{ - portfolios: myPortfolio(t), - securities: mockSecuritiesClientWithData, - }, - args: args{req: connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: "mybank-myportfolio", - })}, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioSnapshot]) bool { - return true && - assert.Equals(t, "US0378331005", r.Msg.Positions["US0378331005"].Security.Id) && - assert.Equals(t, 10, r.Msg.Positions["US0378331005"].Amount) && - assert.Equals(t, portfoliov1.Value(107080), r.Msg.Positions["US0378331005"].PurchaseValue, protocmp.Transform()) && - assert.Equals(t, portfoliov1.Value(10708), r.Msg.Positions["US0378331005"].PurchasePrice, protocmp.Transform()) && - assert.Equals(t, portfoliov1.Value(100000), r.Msg.TotalMarketValue, protocmp.Transform()) - }, - }, - { - name: "happy path, before sell", - fields: fields{ - portfolios: myPortfolio(t), - securities: mockSecuritiesClientWithData, - }, - args: args{req: connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: "mybank-myportfolio", - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC)), - })}, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioSnapshot]) bool { - pos := r.Msg.Positions["US0378331005"] - - return true && - assert.Equals(t, "US0378331005", pos.Security.Id) && - assert.Equals(t, 20, pos.Amount) && - assert.Equals(t, portfoliov1.Value(214160), pos.PurchaseValue, protocmp.Transform()) && - assert.Equals(t, portfoliov1.Value(10708), pos.PurchasePrice, protocmp.Transform()) && - assert.Equals(t, portfoliov1.Value(10000), pos.MarketPrice, protocmp.Transform()) && - assert.Equals(t, portfoliov1.Value(200000), pos.MarketValue, protocmp.Transform()) - }, - }, - { - name: "happy path, position zero'd out", - fields: fields{ - portfolios: zeroPositions(t), - securities: mockSecuritiesClientWithData, - }, - args: args{req: connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: "mybank-myportfolio", - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC)), - })}, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioSnapshot]) bool { - return true && - len(r.Msg.Positions) == 0 - }, - }, - { - name: "events list error", - fields: fields{ - portfolios: emptyPortfolio(t), - events: internal.ErrOps[*portfoliov1.PortfolioEvent](io.EOF), - securities: &mockSecuritiesClient{listSecuritiesError: io.EOF}, - }, - args: args{req: connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: "mybank-myportfolio", - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC)), - })}, - wantErr: true, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioSnapshot]) bool { - return true - }, - }, - { - name: "securities list error", - fields: fields{ - portfolios: myPortfolio(t), - securities: &mockSecuritiesClient{listSecuritiesError: io.EOF}, - }, - args: args{req: connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{ - PortfolioId: "mybank-myportfolio", - Time: timestamppb.New(time.Date(2020, 1, 1, 0, 0, 0, 1, time.UTC)), - })}, - wantErr: true, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioSnapshot]) bool { - return true - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - - if tt.fields.events != nil { - svc.events = tt.fields.events - } - - gotRes, err := svc.GetPortfolioSnapshot(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.GetPortfolioSnapshot() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -type mockSecuritiesClient struct { - securities []*portfoliov1.Security - listSecuritiesError error -} - -func (m *mockSecuritiesClient) ListSecurities(context.Context, *connect.Request[portfoliov1.ListSecuritiesRequest]) (*connect.Response[portfoliov1.ListSecuritiesResponse], error) { - return connect.NewResponse(&portfoliov1.ListSecuritiesResponse{ - Securities: m.securities, - }), m.listSecuritiesError -} - -func (*mockSecuritiesClient) GetSecurity(context.Context, *connect.Request[portfoliov1.GetSecurityRequest]) (*connect.Response[portfoliov1.Security], error) { - return nil, nil -} - -func (*mockSecuritiesClient) CreateSecurity(context.Context, *connect.Request[portfoliov1.CreateSecurityRequest]) (*connect.Response[portfoliov1.Security], error) { - return nil, nil -} - -func (*mockSecuritiesClient) UpdateSecurity(context.Context, *connect.Request[portfoliov1.UpdateSecurityRequest]) (*connect.Response[portfoliov1.Security], error) { - return nil, nil -} - -func (*mockSecuritiesClient) DeleteSecurity(context.Context, *connect.Request[portfoliov1.DeleteSecurityRequest]) (*connect.Response[emptypb.Empty], error) { - return nil, nil -} - -func (*mockSecuritiesClient) TriggerSecurityQuoteUpdate(context.Context, *connect.Request[portfoliov1.TriggerQuoteUpdateRequest]) (*connect.Response[portfoliov1.TriggerQuoteUpdateResponse], error) { - return nil, nil -} diff --git a/service/portfolio/transactions.go b/service/portfolio/transactions.go deleted file mode 100644 index 6f91adcd..00000000 --- a/service/portfolio/transactions.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "bytes" - "context" - "errors" - "log/slog" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/import/csv" - "github.com/oxisto/money-gopher/service/internal/crud" - - "connectrpc.com/connect" - "google.golang.org/protobuf/types/known/emptypb" -) - -var portfolioEventSetter = func(obj *portfoliov1.PortfolioEvent) *portfoliov1.PortfolioEvent { - return obj -} - -var ( - ErrMissingSecurityId = errors.New("the specified transaction type requires a security ID") - ErrMissingPrice = errors.New("a transaction requires a price") - ErrMissingAmount = errors.New("the specified transaction type requires an amount") -) - -func (svc *service) CreatePortfolioTransaction(ctx context.Context, req *connect.Request[portfoliov1.CreatePortfolioTransactionRequest]) (res *connect.Response[portfoliov1.PortfolioEvent], err error) { - var ( - tx *portfoliov1.PortfolioEvent = req.Msg.Transaction - ) - - // Do some basic validation depending on the type - switch tx.Type { - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL: - fallthrough - case portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY: - if tx.SecurityId == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, ErrMissingSecurityId) - } else if tx.Amount == 0 { - return nil, connect.NewError(connect.CodeInvalidArgument, ErrMissingAmount) - } - } - - // We always need a price - if tx.Price.IsZero() { - return nil, connect.NewError(connect.CodeInvalidArgument, ErrMissingPrice) - } - - // Create a unique name for the transaction - tx.MakeUniqueID() - - slog.Info( - "Creating transaction", - "transaction", req.Msg.Transaction, - ) - - return crud.Create( - req.Msg.Transaction, - svc.events, - portfolioEventSetter, - ) -} - -func (svc *service) GetPortfolioTransaction(ctx context.Context, req *connect.Request[portfoliov1.GetPortfolioTransactionRequest]) (res *connect.Response[portfoliov1.PortfolioEvent], err error) { - return crud.Get( - req.Msg.Id, - svc.events, - func(obj *portfoliov1.PortfolioEvent) *portfoliov1.PortfolioEvent { - return obj - }, - ) -} - -func (svc *service) ListPortfolioTransactions(ctx context.Context, req *connect.Request[portfoliov1.ListPortfolioTransactionsRequest]) (res *connect.Response[portfoliov1.ListPortfolioTransactionsResponse], err error) { - return crud.List( - svc.events, - func( - res *connect.Response[portfoliov1.ListPortfolioTransactionsResponse], - list []*portfoliov1.PortfolioEvent, - ) error { - res.Msg.Transactions = list - return nil - }, - req.Msg.PortfolioId, - ) -} - -func (svc *service) UpdatePortfolioTransaction(ctx context.Context, req *connect.Request[portfoliov1.UpdatePortfolioTransactionRequest]) (res *connect.Response[portfoliov1.PortfolioEvent], err error) { - slog.Info( - "Updating transaction", - "tx", req.Msg.Transaction, - "update-mask", req.Msg.UpdateMask.Paths, - ) - - return crud.Update( - req.Msg.Transaction.Id, - req.Msg.Transaction, - req.Msg.UpdateMask.Paths, - svc.events, - portfolioEventSetter, - ) -} - -func (svc *service) DeletePortfolioTransactions(ctx context.Context, req *connect.Request[portfoliov1.DeletePortfolioTransactionRequest]) (res *connect.Response[emptypb.Empty], err error) { - return crud.Delete( - req.Msg.TransactionId, - svc.events, - ) -} - -func (svc *service) ImportTransactions(ctx context.Context, req *connect.Request[portfoliov1.ImportTransactionsRequest]) (res *connect.Response[emptypb.Empty], err error) { - var ( - txs []*portfoliov1.PortfolioEvent - secs []*portfoliov1.Security - ) - - txs, secs = csv.Import(bytes.NewReader([]byte(req.Msg.FromCsv)), req.Msg.PortfolioId) - - for _, sec := range secs { - // TODO(oxisto): Once "Create" is really create and not replace, we need - // to change this to something else. - svc.securities.CreateSecurity( - context.Background(), - connect.NewRequest(&portfoliov1.CreateSecurityRequest{ - Security: sec, - }), - ) - } - - for _, tx := range txs { - err = svc.events.Replace(tx) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - } - - res = connect.NewResponse(&emptypb.Empty{}) - - return -} diff --git a/service/portfolio/transactions_test.go b/service/portfolio/transactions_test.go deleted file mode 100644 index ded7692f..00000000 --- a/service/portfolio/transactions_test.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package portfolio - -import ( - "context" - "testing" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "google.golang.org/protobuf/types/known/fieldmaskpb" -) - -func Test_service_CreatePortfolioTransaction(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.CreatePortfolioTransactionRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.PortfolioEvent]] - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path buy", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.CreatePortfolioTransactionRequest{ - Transaction: &portfoliov1.PortfolioEvent{ - PortfolioId: "mybank-myportfolio", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - SecurityId: "My Security", - Amount: 1, - Price: portfoliov1.Value(2000), - }, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioEvent]) bool { - return assert.Equals(t, "My Security", r.Msg.GetSecurityId()) - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.events.List("mybank-myportfolio") - return assert.Equals(t, 3, len(list)) - }, - }, - { - name: "happy path sell", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.CreatePortfolioTransactionRequest{ - Transaction: &portfoliov1.PortfolioEvent{ - PortfolioId: "mybank-myportfolio", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, - SecurityId: "My Security", - Amount: 1, - Price: portfoliov1.Value(2000), - }, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioEvent]) bool { - return assert.Equals(t, "My Security", r.Msg.GetSecurityId()) - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.events.List("mybank-myportfolio") - return assert.Equals(t, 3, len(list)) - }, - }, - { - name: "missing security ID", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.CreatePortfolioTransactionRequest{ - Transaction: &portfoliov1.PortfolioEvent{ - PortfolioId: "mybank-myportfolio", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_SELL, - Amount: 1, - Price: portfoliov1.Value(2000), - }, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioEvent]) bool { - if r != nil { - t.Fatal("not nil") - } - - return true - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.CreatePortfolioTransaction(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.CreatePortfolioTransaction() error = %v, wantErr %v", err, tt.wantErr) - return - } - - tt.wantRes(t, gotRes) - if tt.wantSvc != nil { - tt.wantSvc(t, svc) - } - }) - } -} - -func Test_service_GetPortfolioTransaction(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.GetPortfolioTransactionRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.PortfolioEvent]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.GetPortfolioTransactionRequest{ - Id: "buy", - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioEvent]) bool { - return assert.Equals(t, "buy", r.Msg.Id) && assert.Equals(t, "mybank-myportfolio", r.Msg.PortfolioId) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.GetPortfolioTransaction(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.GetPortfolioTransaction() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_ListPortfolioTransactions(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.ListPortfolioTransactionsRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.ListPortfolioTransactionsResponse]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.ListPortfolioTransactionsRequest{ - PortfolioId: "mybank-myportfolio", - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.ListPortfolioTransactionsResponse]) bool { - return assert.Equals(t, 2, len(r.Msg.Transactions)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.ListPortfolioTransactions(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.ListPortfolioTransactions() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_UpdatePortfolioTransaction(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.UpdatePortfolioTransactionRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.PortfolioEvent]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.UpdatePortfolioTransactionRequest{ - Transaction: &portfoliov1.PortfolioEvent{ - Id: "buy", - Type: portfoliov1.PortfolioEventType_PORTFOLIO_EVENT_TYPE_BUY, - SecurityId: "My Second Security", - }, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"security_id"}}, - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.PortfolioEvent]) bool { - return assert.Equals(t, "My Second Security", r.Msg.SecurityId) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - gotRes, err := svc.UpdatePortfolioTransaction(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.UpdatePortfolioTransactions() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_DeletePortfolioTransactions(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.DeletePortfolioTransactionRequest] - } - tests := []struct { - name string - fields fields - args args - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: myPortfolio(t), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.DeletePortfolioTransactionRequest{ - TransactionId: 1, - }), - }, - wantSvc: func(t *testing.T, s *service) bool { - list, _ := s.portfolios.List("mybank-myportfolio") - return assert.Equals(t, 0, len(list)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - _, err := svc.DeletePortfolioTransactions(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.DeletePortfolioTransactions() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantSvc(t, svc) - }) - } -} - -func Test_service_ImportTransactions(t *testing.T) { - type fields struct { - portfolios persistence.StorageOperations[*portfoliov1.Portfolio] - securities portfoliov1connect.SecuritiesServiceClient - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.ImportTransactionsRequest] - } - tests := []struct { - name string - fields fields - args args - wantSvc assert.Want[*service] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - portfolios: emptyPortfolio(t), - securities: &mockSecuritiesClient{}, - }, - args: args{ - req: connect.NewRequest(&portfoliov1.ImportTransactionsRequest{ - PortfolioId: "mybank-myportfolio", - FromCsv: `Date;Type;Value;Transaction Currency;Gross Amount;Currency Gross Amount;Exchange Rate;Fees;Taxes;Shares;ISIN;WKN;Ticker Symbol;Security Name;Note -2021-06-05T00:00;Buy;2.151,85;EUR;;;;10,25;0,00;20;US0378331005;865985;APC.F;Apple Inc.; -2021-06-05T00:00;Sell;-2.151,85;EUR;;;;10,25;0,00;20;US0378331005;865985;APC.F;Apple Inc.; -2021-06-18T00:00;Buy;912,66;EUR;;;;7,16;0,00;5;US09075V1026;A2PSR2;22UA.F;BioNTech SE;`, - }), - }, - wantSvc: func(t *testing.T, s *service) bool { - txs, err := s.events.List("mybank-myportfolio") - return true && - assert.NoError(t, err) && - assert.Equals(t, 3, len(txs)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - portfolios: tt.fields.portfolios, - events: persistence.Relationship[*portfoliov1.PortfolioEvent](tt.fields.portfolios), - securities: tt.fields.securities, - } - _, err := svc.ImportTransactions(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.ImportTransactions() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantSvc(t, svc) - }) - } -} diff --git a/service/securities/quote.go b/service/securities/quote.go deleted file mode 100644 index 13b5107f..00000000 --- a/service/securities/quote.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package securities - -import ( - "context" - "log/slog" - "time" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - - "connectrpc.com/connect" - "github.com/lmittmann/tint" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func (svc *service) TriggerSecurityQuoteUpdate(ctx context.Context, req *connect.Request[portfoliov1.TriggerQuoteUpdateRequest]) (res *connect.Response[portfoliov1.TriggerQuoteUpdateResponse], err error) { - var ( - sec *portfoliov1.Security - qp QuoteProvider - ok bool - ) - - // TODO(oxisto): Support a "list" with filtered values instead - for _, name := range req.Msg.SecurityIds { - // Fetch security - sec, err = svc.fetchSecurity(name) - if err != nil { - return nil, connect.NewError(connect.CodeInternal, err) - } - - res = connect.NewResponse(&portfoliov1.TriggerQuoteUpdateResponse{}) - - if sec.QuoteProvider == nil { - slog.Warn("No quote provider configured for security", "security", sec.Id) - return - } - - qp, ok = providers[*sec.QuoteProvider] - if !ok { - return - } - - // Trigger update from quote provider in separate go-routine - // TODO(oxisto): Use sync/errgroup instead - for idx := range sec.ListedOn { - idx := idx - go func() { - ls := sec.ListedOn[idx] - - slog.Debug("Triggering quote update", "security", ls, "provider", *sec.QuoteProvider) - - err = svc.updateQuote(qp, ls) - if err != nil { - slog.Error("An error occurred during quote update", tint.Err(err), "ls", ls) - } - }() - } - } - - return -} - -func (svc *service) updateQuote(qp QuoteProvider, ls *portfoliov1.ListedSecurity) (err error) { - var ( - quote *portfoliov1.Currency - t time.Time - ctx context.Context - cancel context.CancelFunc - ) - - ctx, cancel = context.WithTimeout(context.Background(), time.Second*60) - defer cancel() - - quote, t, err = qp.LatestQuote(ctx, ls) - if err != nil { - return err - } - - ls.LatestQuote = quote - ls.LatestQuoteTimestamp = timestamppb.New(t) - - _, err = svc.listedSecurities.Update( - []any{ls.SecurityId, ls.Ticker}, - ls, []string{"latest_quote", "latest_quote_timestamp"}, - ) - if err != nil { - return err - } - - return -} diff --git a/service/securities/quote_test.go b/service/securities/quote_test.go deleted file mode 100644 index d36dc530..00000000 --- a/service/securities/quote_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package securities - -import ( - "context" - "testing" - "time" - - moneygopher "github.com/oxisto/money-gopher" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "golang.org/x/text/currency" -) - -const QuoteProviderMock = "mock" - -type mockQP struct { -} - -func (m *mockQP) LatestQuote(ctx context.Context, ls *portfoliov1.ListedSecurity) (quote *portfoliov1.Currency, t time.Time, err error) { - return portfoliov1.Value(100), time.Now(), nil -} - -func Test_service_TriggerSecurityQuoteUpdate(t *testing.T) { - RegisterQuoteProvider(QuoteProviderMock, &mockQP{}) - - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.TriggerQuoteUpdateRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*portfoliov1.TriggerQuoteUpdateResponse] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - ops.Replace(&portfoliov1.Security{ - Id: "My Security", - QuoteProvider: moneygopher.Ref("mock"), - }) - rel := persistence.Relationship[*portfoliov1.ListedSecurity](ops) - assert.NoError(t, rel.Replace(&portfoliov1.ListedSecurity{ - SecurityId: "My Security", - Ticker: "SEC", - Currency: currency.EUR.String(), - })) - }), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.TriggerQuoteUpdateRequest{ - SecurityIds: []string{"My Security"}, - }), - }, - wantRes: func(t *testing.T, tqur *portfoliov1.TriggerQuoteUpdateResponse) bool { - return true - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - listedSecurities: persistence.Relationship[*portfoliov1.ListedSecurity](tt.fields.securities), - } - gotRes, err := svc.TriggerSecurityQuoteUpdate(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.TriggerSecurityQuoteUpdate() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes.Msg) - }) - } -} - -type mockQuoteProvider struct{} - -func (mockQuoteProvider) LatestQuote(_ context.Context, _ *portfoliov1.ListedSecurity) (quote *portfoliov1.Currency, t time.Time, err error) { - return portfoliov1.Value(100), time.Date(1, 0, 0, 0, 0, 0, 0, time.UTC), nil -} - -func Test_service_updateQuote(t *testing.T) { - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - } - type args struct { - qp QuoteProvider - ls *portfoliov1.ListedSecurity - } - tests := []struct { - name string - fields fields - args args - want assert.Want[*portfoliov1.ListedSecurity] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - ops.Replace(&portfoliov1.Security{Id: "My Security"}) - rel := persistence.Relationship[*portfoliov1.ListedSecurity](ops) - assert.NoError(t, rel.Replace(&portfoliov1.ListedSecurity{SecurityId: "My Security", Ticker: "SEC", Currency: currency.EUR.String()})) - }), - }, - args: args{ - qp: &mockQuoteProvider{}, - ls: &portfoliov1.ListedSecurity{SecurityId: "My Security", Ticker: "SEC", Currency: currency.EUR.String()}, - }, - want: func(t *testing.T, ls *portfoliov1.ListedSecurity) bool { - return assert.Equals(t, 100, int(ls.LatestQuote.Value)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - listedSecurities: persistence.Relationship[*portfoliov1.ListedSecurity](tt.fields.securities), - } - if err := svc.updateQuote(tt.args.qp, tt.args.ls); (err != nil) != tt.wantErr { - t.Errorf("updateQuote() error = %v, wantErr %v", err, tt.wantErr) - } - - tt.want(t, tt.args.ls) - }) - } -} diff --git a/service/securities/securities.go b/service/securities/securities.go deleted file mode 100644 index 75c0db48..00000000 --- a/service/securities/securities.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package securities - -import ( - "context" - "slices" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/service/internal/crud" - - "connectrpc.com/connect" - "google.golang.org/protobuf/types/known/emptypb" -) - -func (svc *service) CreateSecurity(ctx context.Context, req *connect.Request[portfoliov1.CreateSecurityRequest]) (res *connect.Response[portfoliov1.Security], err error) { - return crud.Create( - req.Msg.Security, - svc.securities, - func(obj *portfoliov1.Security) *portfoliov1.Security { - for _, ls := range obj.ListedOn { - svc.listedSecurities.Replace(ls) - } - - return obj - }, - ) -} - -func (svc *service) GetSecurity(ctx context.Context, req *connect.Request[portfoliov1.GetSecurityRequest]) (res *connect.Response[portfoliov1.Security], err error) { - return crud.Get( - req.Msg.Id, - svc.securities, - func(obj *portfoliov1.Security) *portfoliov1.Security { - obj.ListedOn, _ = svc.listedSecurities.List(obj.Id) - - return obj - }, - ) -} - -func (svc *service) ListSecurities(ctx context.Context, req *connect.Request[portfoliov1.ListSecuritiesRequest]) (res *connect.Response[portfoliov1.ListSecuritiesResponse], err error) { - return crud.List( - svc.securities, - func(res *connect.Response[portfoliov1.ListSecuritiesResponse], list []*portfoliov1.Security) error { - res.Msg.Securities = list - - for _, sec := range res.Msg.Securities { - sec.ListedOn, err = svc.listedSecurities.List(sec.Id) - if err != nil { - return err - } - } - - return nil - }, - ) -} - -func (svc *service) UpdateSecurity(ctx context.Context, req *connect.Request[portfoliov1.UpdateSecurityRequest]) (res *connect.Response[portfoliov1.Security], err error) { - return crud.Update( - req.Msg.Security.Id, - req.Msg.Security, - req.Msg.UpdateMask.Paths, - svc.securities, - func(obj *portfoliov1.Security) *portfoliov1.Security { - if slices.Contains(req.Msg.UpdateMask.Paths, "listed_on") { - for _, ls := range req.Msg.Security.ListedOn { - svc.listedSecurities.Replace(ls) - } - } - - return obj - }, - ) -} - -func (svc *service) DeleteSecurity(ctx context.Context, req *connect.Request[portfoliov1.DeleteSecurityRequest]) (res *connect.Response[emptypb.Empty], err error) { - return crud.Delete( - req.Msg.Id, - svc.securities, - ) -} - -func (svc *service) fetchSecurity(name string) (sec *portfoliov1.Security, err error) { - res, err := crud.Get( - name, - svc.securities, - func(obj *portfoliov1.Security) *portfoliov1.Security { - obj.ListedOn, _ = svc.listedSecurities.List(obj.Id) - - return obj - }, - ) - if err != nil { - return nil, err - } - - return res.Msg, nil -} diff --git a/service/securities/securities_test.go b/service/securities/securities_test.go deleted file mode 100644 index 0f6883a2..00000000 --- a/service/securities/securities_test.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package securities - -import ( - "context" - "testing" - - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/internal" - "github.com/oxisto/money-gopher/persistence" - - "connectrpc.com/connect" - "github.com/oxisto/assert" - "golang.org/x/text/currency" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/emptypb" - "google.golang.org/protobuf/types/known/fieldmaskpb" -) - -func Test_service_ListSecurities(t *testing.T) { - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.ListSecuritiesRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*connect.Response[portfoliov1.ListSecuritiesResponse]] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - assert.NoError(t, ops.Replace(&portfoliov1.Security{Id: "My Security"})) - rel := persistence.Relationship[*portfoliov1.ListedSecurity](ops) - assert.NoError(t, rel.Replace(&portfoliov1.ListedSecurity{SecurityId: "My Security", Ticker: "SEC", Currency: currency.EUR.String()})) - }), - }, - wantRes: func(t *testing.T, r *connect.Response[portfoliov1.ListSecuritiesResponse]) bool { - return true && - assert.Equals(t, "My Security", r.Msg.Securities[0].Id) && - assert.Equals(t, 1, len(r.Msg.Securities[0].ListedOn)) - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - listedSecurities: persistence.Relationship[*portfoliov1.ListedSecurity](tt.fields.securities), - } - gotRes, err := svc.ListSecurities(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.ListSecurities() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes) - }) - } -} - -func Test_service_GetSecurity(t *testing.T) { - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.GetSecurityRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*portfoliov1.Security] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - ops.Replace(&portfoliov1.Security{Id: "My Security"}) - rel := persistence.Relationship[*portfoliov1.ListedSecurity](ops) - assert.NoError(t, rel.Replace(&portfoliov1.ListedSecurity{SecurityId: "My Security", Ticker: "SEC", Currency: currency.EUR.String()})) - }), - }, - args: args{ - req: connect.NewRequest(&portfoliov1.GetSecurityRequest{Id: "My Security"}), - }, - wantRes: func(t *testing.T, s *portfoliov1.Security) bool { - return assert.Equals(t, &portfoliov1.Security{ - Id: "My Security", - ListedOn: []*portfoliov1.ListedSecurity{{SecurityId: "My Security", Ticker: "SEC", Currency: currency.EUR.String()}}, - }, s, protocmp.Transform()) - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - listedSecurities: persistence.Relationship[*portfoliov1.ListedSecurity](tt.fields.securities), - } - gotRes, err := svc.GetSecurity(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.GetSecurity() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes.Msg) - }) - } -} - -func Test_service_UpdateSecurity(t *testing.T) { - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.UpdateSecurityRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes *connect.Response[portfoliov1.Security] - wantErr bool - }{ - { - name: "change display_name", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - ops.Replace(&portfoliov1.Security{Id: "My Stock"}) - }), - }, - args: args{req: connect.NewRequest(&portfoliov1.UpdateSecurityRequest{ - Security: &portfoliov1.Security{Id: "My Stock", DisplayName: "Test"}, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"display_name"}}, - })}, - wantRes: connect.NewResponse(&portfoliov1.Security{Id: "My Stock", DisplayName: "Test"}), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - } - gotRes, err := svc.UpdateSecurity(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.UpdateSecurity() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !proto.Equal(gotRes.Msg, tt.wantRes.Msg) { - t.Errorf("service.UpdateSecurity() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func Test_service_DeleteSecurity(t *testing.T) { - type fields struct { - securities persistence.StorageOperations[*portfoliov1.Security] - UnimplementedSecuritiesServiceHandler portfoliov1connect.UnimplementedSecuritiesServiceHandler - } - type args struct { - ctx context.Context - req *connect.Request[portfoliov1.DeleteSecurityRequest] - } - tests := []struct { - name string - fields fields - args args - wantRes assert.Want[*emptypb.Empty] - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - securities: internal.NewTestDBOps(t, func(ops persistence.StorageOperations[*portfoliov1.Security]) { - ops.Replace(&portfoliov1.Security{Id: "My Stock"}) - }), - }, - args: args{req: connect.NewRequest(&portfoliov1.DeleteSecurityRequest{ - Id: "My Stock", - })}, - wantRes: func(t *testing.T, e *emptypb.Empty) bool { - return assert.Equals(t, &emptypb.Empty{}, e, protocmp.Transform()) - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &service{ - securities: tt.fields.securities, - } - gotRes, err := svc.DeleteSecurity(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("service.DeleteSecurityRequest() error = %v, wantErr %v", err, tt.wantErr) - return - } - tt.wantRes(t, gotRes.Msg) - }) - } -} diff --git a/service/securities/service.go b/service/securities/service.go deleted file mode 100644 index 1be7611e..00000000 --- a/service/securities/service.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -// package securities contains the code for the SecuritiesService implementation. -package securities - -import ( - "time" - - moneygopher "github.com/oxisto/money-gopher" - portfoliov1 "github.com/oxisto/money-gopher/gen" - "github.com/oxisto/money-gopher/gen/portfoliov1connect" - "github.com/oxisto/money-gopher/persistence" - - "golang.org/x/text/currency" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type service struct { - securities persistence.StorageOperations[*portfoliov1.Security] - listedSecurities persistence.StorageOperations[*portfoliov1.ListedSecurity] - - portfoliov1connect.UnimplementedSecuritiesServiceHandler -} - -func NewService(db *persistence.DB) portfoliov1connect.SecuritiesServiceHandler { - securities := persistence.Ops[*portfoliov1.Security](db) - listedSecurities := persistence.Relationship[*portfoliov1.ListedSecurity](securities) - secs := []*portfoliov1.Security{ - { - Id: "US0378331005", - DisplayName: "Apple Inc.", - ListedOn: []*portfoliov1.ListedSecurity{ - { - SecurityId: "US0378331005", - Ticker: "APC.F", - Currency: currency.EUR.String(), - LatestQuote: portfoliov1.Value(15016), - LatestQuoteTimestamp: timestamppb.New(time.Date(2023, 4, 21, 0, 0, 0, 0, time.Local)), - }, - { - SecurityId: "US0378331005", - Ticker: "AAPL", - Currency: currency.USD.String(), - LatestQuote: portfoliov1.Value(16502), - LatestQuoteTimestamp: timestamppb.New(time.Date(2023, 4, 21, 0, 0, 0, 0, time.Local)), - }, - }, - QuoteProvider: moneygopher.Ref(QuoteProviderYF), - }, - } - for _, sec := range secs { - securities.Replace(sec) - - // TODO: in the future, we might do this automatically - for _, ls := range sec.ListedOn { - listedSecurities.Replace(ls) - } - } - - return &service{ - securities: securities, - listedSecurities: listedSecurities, - } -} diff --git a/service/securities/service_test.go b/service/securities/service_test.go deleted file mode 100644 index a3cb92c9..00000000 --- a/service/securities/service_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 Christian Banse -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is part of The Money Gopher. - -package securities - -import ( - "testing" - - "github.com/oxisto/money-gopher/internal" - - "github.com/oxisto/assert" -) - -func TestNewService(t *testing.T) { - tests := []struct { - name string - want assert.Want[*service] - }{ - { - name: "Default", - want: func(t *testing.T, s *service) bool { - return true - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewService(internal.NewTestDB(t)) - tt.want(t, assert.Is[*service](t, got)) - }) - } -} diff --git a/sqlc.yaml b/sqlc.yaml index 88f9c706..9a0d4fa1 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -8,3 +8,53 @@ sql: package: "persistence" out: "persistence" emit_result_struct_pointers: true + emit_pointers_for_null_types: true + overrides: + - column: "accounts.type" + go_type: github.com/oxisto/money-gopher/portfolio/accounts.AccountType + - column: "portfolio_events.type" + go_type: github.com/oxisto/money-gopher/portfolio/events.PortfolioEventType + - column: "portfolio_events.price" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "portfolio_events.fees" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "portfolio_events.taxes" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "transactions.type" + go_type: github.com/oxisto/money-gopher/portfolio/events.PortfolioEventType + - column: "transactions.price" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "transactions.fees" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "transactions.taxes" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true + - column: "listed_securities.latest_quote" + go_type: + import: "github.com/oxisto/money-gopher/currency" + package: "currency" + type: "Currency" + pointer: true diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 00000000..c785ddb5 --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,82 @@ +module tools + +go 1.23.4 + +require ( + github.com/99designs/gqlgen v0.17.61 + github.com/mfridman/tparse v0.16.0 + github.com/sqlc-dev/sqlc v1.27.0 + golang.org/x/tools v0.24.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/agnivade/levenshtein v1.2.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v1.0.0 // indirect + github.com/charmbracelet/x/ansi v0.4.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/cubicdaiya/gonp v1.0.4 // indirect + github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/cel-go v0.21.0 // indirect + github.com/google/uuid v1.6.0 // 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/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/buildversion v0.3.0 // indirect + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pganalyze/pg_query_go/v5 v5.1.0 // indirect + github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 // indirect + github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect + github.com/pingcap/log v1.1.0 // indirect + github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/riza-io/grpc-go v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/tetratelabs/wazero v1.7.3 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect + github.com/vektah/gqlparser/v2 v2.5.20 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20240606042535-c0843d6592cc // indirect + github.com/wasilibs/wazero-helpers v0.0.0-20240604052452-61d7981e9a38 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.31.1 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 00000000..533889cf --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,242 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/99designs/gqlgen v0.17.61 h1:vE7xLRC066n9wehgjeplILOWtwz75zbzcV2/Iv9i3pw= +github.com/99designs/gqlgen v0.17.61/go.mod h1:rFU1T3lhv/tPeAlww/DJ4ol2YxT/pPpue+xxPbkd3r4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= +github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= +github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= +github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +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/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/go-cmp v0.5.5/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/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/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/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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/buildversion v0.3.0 h1:hehEX3IbBZJBqquXctUEOWJfIM46P0ku9naK9h1BGuY= +github.com/mfridman/buildversion v0.3.0/go.mod h1:sfXvYxwfmLvkklTJLv9xJ0Wffw57z9ZFOK4KOGJYafU= +github.com/mfridman/tparse v0.16.0 h1:loy4AVPJPMdqdS6T9Xwnpfct8yhjmGOfTipHCFk62LE= +github.com/mfridman/tparse v0.16.0/go.mod h1:yw5mav2iN2rCf3/DSQxFQy3MuyQoWAQagjwOHdgCWpo= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pganalyze/pg_query_go/v5 v5.1.0 h1:MlxQqHZnvA3cbRQYyIrjxEjzo560P6MyTgtlaf3pmXg= +github.com/pganalyze/pg_query_go/v5 v5.1.0/go.mod h1:FsglvxidZsVN+Ltw3Ai6nTgPVcK2BPukH3jCDEqc1Ug= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 h1:+FZIDR/D97YOPik4N4lPDaUcLDF/EQPogxtlHB2ZZRM= +github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c h1:CgbKAHto5CQgWM9fSBIvaxsJHuGP0uM74HXtv3MyyGQ= +github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= +github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= +github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= +github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1 h1:SwGY3zMnK4wO85vvRIqrR3Yh6VpIC9pydG0QNOUPHCY= +github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1/go.mod h1:yRkiqLFwIqibYg2P7h4bclHjHcJiIFRLKhGRyBcKYus= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= +github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +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/sqlc-dev/sqlc v1.27.0 h1:wWc+401GLh0whLa30WmDkkl11lMBZuqvDvgu5OsaDiQ= +github.com/sqlc-dev/sqlc v1.27.0/go.mod h1:wXAlx++Ed1eUhMeEKyXfeCO+ogPIN1adG5DdPavR4k0= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= +github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/vektah/gqlparser/v2 v2.5.20 h1:kPaWbhBntxoZPaNdBaIPT1Kh0i1b/onb5kXgEdP5JCo= +github.com/vektah/gqlparser/v2 v2.5.20/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM= +github.com/wasilibs/go-pgquery v0.0.0-20240606042535-c0843d6592cc h1:Hgim1Xgk1+viV7p0aZh9OOrMRfG+E4mGA+JsI2uB0+k= +github.com/wasilibs/go-pgquery v0.0.0-20240606042535-c0843d6592cc/go.mod h1:ah6UfXIl/oA0K3SbourB/UHggVJOBXwPZ2XudDmmFac= +github.com/wasilibs/wazero-helpers v0.0.0-20240604052452-61d7981e9a38 h1:RBu75fhabyxyGJ2zhkoNuRyObBMhVeMoXqmeaPTg2CQ= +github.com/wasilibs/wazero-helpers v0.0.0-20240604052452-61d7981e9a38/go.mod h1:Z80JvMwvze8KUlVQIdw9L7OSskZJ1yxlpi4AQhoQe4s= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +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.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +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.2.8/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.0-20210107192922-496545a6307b/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= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= +modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 00000000..585622a6 --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,11 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/99designs/gqlgen" + _ "github.com/mfridman/tparse" + _ "github.com/sqlc-dev/sqlc/cmd/sqlc" + _ "golang.org/x/tools/cmd/stringer" +)