From 55c6c14630b4c39a71a062a37b7790bb4a13338c Mon Sep 17 00:00:00 2001 From: optclblast Date: Sun, 26 May 2024 21:20:14 +0300 Subject: [PATCH] msigs fetch implemented --- backend/README.md | 50 +++++- backend/internal/factory/repositories.go | 4 +- backend/internal/factory/wire_gen.go | 4 +- .../interface/rest/controllers/chain.go | 17 +- .../interface/rest/presenters/participants.go | 18 ++- .../internal/interface/rest/presenters/tx.go | 55 ++++++- .../usecase/interactors/chain/chain.go | 19 ++- .../repository/transactions/repository.go | 152 +++++++++++++++++- backend/migrations/blockd.sql | 2 + 9 files changed, 300 insertions(+), 21 deletions(-) diff --git a/backend/README.md b/backend/README.md index 38f34ee..2f6e2e5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -406,16 +406,58 @@ curl --request GET \ Response: ``` json { - // todo + "_type": "multisigs", + "_links": { + "self": { + "href": "/organizations/018fb61b-9f79-705a-bd92-59233ed15ac7/multisig" + } + }, + "multisigs": [ + { + "id": "018fb61e-6c64-7a70-b677-992353389731", + "title": "new sig", + "owners": { + "_type": "participants", + "_links": { + "self": { + "href": "/organizations/018fb61b-9f79-705a-bd92-59233ed15ac7/participants" + } + }, + "participants": [ + { + "_type": "participant", + "_links": { + "self": { + "href": "/organizations/018fb61b-9f79-705a-bd92-59233ed15ac7/participants/018fb61b-76cb-71c1-8306-cea167411ac8" + } + }, + "id": "018fb61b-76cb-71c1-8306-cea167411ac8", + "name": "Bladee The Grand Drainer", + "credentials": { + "email": "bladeee@gmail.com", + "phone": "+79999999999", + "telegram": "@thebladee" + }, + "created_at": 1716758014713, + "updated_at": 1716758014713, + "is_user": true, + "is_admin": true, + "is_owner": true, + "is_active": true + } + ] + } + } + ] } ``` -## GET **/organizations/{organization_id}/payrolls** -Fetch payrolls - ## POST **/organizations/{organization_id}/payrolls** New payroll +## GET **/organizations/{organization_id}/payrolls** +Fetch payrolls + ## GET **/organizations/{organization_id}/license** Fetch licenses diff --git a/backend/internal/factory/repositories.go b/backend/internal/factory/repositories.go index be61dde..b67a26f 100644 --- a/backend/internal/factory/repositories.go +++ b/backend/internal/factory/repositories.go @@ -24,8 +24,8 @@ func provideOrganizationsRepository( return organizations.NewRepository(db, uRepo) } -func provideTxRepository(db *sql.DB) transactions.Repository { - return transactions.NewRepository(db) +func provideTxRepository(db *sql.DB, or organizations.Repository) transactions.Repository { + return transactions.NewRepository(db, or) } func provideAuthRepository(db *sql.DB) auth.Repository { diff --git a/backend/internal/factory/wire_gen.go b/backend/internal/factory/wire_gen.go index ca403a5..e630c1b 100644 --- a/backend/internal/factory/wire_gen.go +++ b/backend/internal/factory/wire_gen.go @@ -21,14 +21,14 @@ func ProvideService(c config.Config) (service.Service, func(), error) { return nil, nil, err } usersRepository := provideUsersRepository(db) - transactionsRepository := provideTxRepository(db) + organizationsRepository := provideOrganizationsRepository(db, usersRepository) + transactionsRepository := provideTxRepository(db, organizationsRepository) chainInteractor := provideChainInteractor(logger, c, transactionsRepository) usersInteractor := provideUsersInteractor(logger, usersRepository, chainInteractor) authRepository := provideAuthRepository(db) jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository) authPresenter := provideAuthPresenter(jwtInteractor) authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository) - organizationsRepository := provideOrganizationsRepository(db, usersRepository) client, cleanup2 := provideRedisConnection(c) cache := provideRedisCache(client, logger) organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache) diff --git a/backend/internal/interface/rest/controllers/chain.go b/backend/internal/interface/rest/controllers/chain.go index 3443c96..3d13fb6 100644 --- a/backend/internal/interface/rest/controllers/chain.go +++ b/backend/internal/interface/rest/controllers/chain.go @@ -222,7 +222,7 @@ func (c *transactionsController) NewMultisig(w http.ResponseWriter, r *http.Requ slog.Any("req", req), ) - ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() ownersPKs := make([][]byte, len(req.Owners)) @@ -246,6 +246,7 @@ func (c *transactionsController) NewMultisig(w http.ResponseWriter, r *http.Requ } if err := c.chainInteractor.NewMultisig(ctx, chain.NewMultisigParams{ + Title: req.Title, Owners: participants, Confirmations: req.Confirmations, }); err != nil { @@ -256,7 +257,19 @@ func (c *transactionsController) NewMultisig(w http.ResponseWriter, r *http.Requ } func (s *transactionsController) ListMultisigs(w http.ResponseWriter, r *http.Request) ([]byte, error) { - return nil, nil + organizationID, err := ctxmeta.OrganizationId(r.Context()) + if err != nil { + return nil, fmt.Errorf("error fetch organization ID from context. %w", err) + } + + msgs, err := s.chainInteractor.ListMultisigs(r.Context(), chain.ListMultisigsParams{ + OrganizationID: organizationID, + }) + if err != nil { + return nil, err + } + + return s.txPresenter.ResponseMultisigs(r.Context(), msgs) } // todo creates a new payout diff --git a/backend/internal/interface/rest/presenters/participants.go b/backend/internal/interface/rest/presenters/participants.go index 1e75fff..f38ae50 100644 --- a/backend/internal/interface/rest/presenters/participants.go +++ b/backend/internal/interface/rest/presenters/participants.go @@ -20,6 +20,14 @@ type ParticipantsPresenter interface { ctx context.Context, participant models.OrganizationParticipant, ) ([]byte, error) + ResponseParticipantsHal( + ctx context.Context, + participants []models.OrganizationParticipant, + ) (*hal.Resource, error) + ResponseParticipantHal( + ctx context.Context, + participant models.OrganizationParticipant, + ) (*hal.Resource, error) } type participantsPresenter struct{} @@ -28,7 +36,7 @@ func NewParticipantsPresenter() ParticipantsPresenter { return new(participantsPresenter) } -func (p *participantsPresenter) responseParticipant( +func (p *participantsPresenter) ResponseParticipantHal( ctx context.Context, participant models.OrganizationParticipant, ) (*hal.Resource, error) { @@ -78,14 +86,14 @@ func (p *participantsPresenter) responseParticipant( return r, nil } -func (p *participantsPresenter) responseParticipants( +func (p *participantsPresenter) ResponseParticipantsHal( ctx context.Context, participants []models.OrganizationParticipant, ) (*hal.Resource, error) { resources := make([]*hal.Resource, len(participants)) for i, pt := range participants { - r, err := p.responseParticipant(ctx, pt) + r, err := p.ResponseParticipantHal(ctx, pt) if err != nil { return nil, fmt.Errorf("error map participant to hal resource. %w", err) } @@ -113,7 +121,7 @@ func (p *participantsPresenter) ResponseListParticipants( ctx context.Context, participants []models.OrganizationParticipant, ) ([]byte, error) { - r, err := p.responseParticipants(ctx, participants) + r, err := p.ResponseParticipantsHal(ctx, participants) if err != nil { return nil, fmt.Errorf("error map participants to hal. %w", err) } @@ -130,7 +138,7 @@ func (p *participantsPresenter) ResponseParticipant( ctx context.Context, participant models.OrganizationParticipant, ) ([]byte, error) { - r, err := p.responseParticipant(ctx, participant) + r, err := p.ResponseParticipantHal(ctx, participant) if err != nil { return nil, fmt.Errorf("error map participant to hal resource. %w", err) } diff --git a/backend/internal/interface/rest/presenters/tx.go b/backend/internal/interface/rest/presenters/tx.go index d2fc82c..d8439b2 100644 --- a/backend/internal/interface/rest/presenters/tx.go +++ b/backend/internal/interface/rest/presenters/tx.go @@ -24,13 +24,18 @@ type TransactionsPresenter interface { ResponseNewTransaction(ctx context.Context, tx *models.Transaction) ([]byte, error) ResponseTransactionsArray(ctx context.Context, txs []*models.Transaction) ([]*hal.Resource, error) ResponseListTransactions(ctx context.Context, txs []*models.Transaction, cursor string) ([]byte, error) + + ResponseMultisigs(ctx context.Context, msgs []models.Multisig) ([]byte, error) } type transactionsPresenter struct { + participantsPresenter ParticipantsPresenter } func NewTransactionsPresenter() TransactionsPresenter { - return &transactionsPresenter{} + return &transactionsPresenter{ + participantsPresenter: NewParticipantsPresenter(), + } } // RequestTransaction returns a Transaction model WITHOUT CreatedBy user set. CreatedAt set as time.Now() @@ -150,7 +155,7 @@ func (p *transactionsPresenter) ResponseListTransactions( r := hal.NewResource( txsResource, "/organizations/"+organizationID.String()+"/transactions", - hal.WithType("organizations"), + hal.WithType("transactions"), ) out, err := json.Marshal(r) @@ -177,3 +182,49 @@ func (c *transactionsPresenter) ResponseNewTransaction( return out, nil } + +type Multisig struct { + ID string `json:"id"` + Title string `json:"title"` + Owners *hal.Resource `json:"owners"` +} + +func (c *transactionsPresenter) ResponseMultisigs(ctx context.Context, msgs []models.Multisig) ([]byte, error) { + organizationID, err := ctxmeta.OrganizationId(ctx) + if err != nil { + return nil, fmt.Errorf("error fetch organization id from context. %w", err) + } + + outArray := make([]Multisig, len(msgs)) + + for i, m := range msgs { + mout := Multisig{ + ID: m.ID.String(), + Title: m.Title, + } + + partOut, err := c.participantsPresenter.ResponseParticipantsHal(ctx, m.Owners) + if err != nil { + return nil, err + } + + mout.Owners = partOut + + outArray[i] = mout + } + + txsResource := map[string]any{"multisigs": outArray} + + r := hal.NewResource( + txsResource, + "/organizations/"+organizationID.String()+"/multisig", + hal.WithType("multisigs"), + ) + + out, err := json.Marshal(r) + if err != nil { + return nil, fmt.Errorf("error marshal tx to hal resource. %w", err) + } + + return out, nil +} diff --git a/backend/internal/usecase/interactors/chain/chain.go b/backend/internal/usecase/interactors/chain/chain.go index 071e5ba..b9dbeab 100644 --- a/backend/internal/usecase/interactors/chain/chain.go +++ b/backend/internal/usecase/interactors/chain/chain.go @@ -20,6 +20,7 @@ import ( type ChainInteractor interface { NewMultisig(ctx context.Context, params NewMultisigParams) error + ListMultisigs(ctx context.Context, params ListMultisigsParams) ([]models.Multisig, error) PubKey(ctx context.Context, user *models.User) ([]byte, error) SalaryDeploy(ctx context.Context, firtsAdmin models.OrganizationParticipant) error } @@ -223,4 +224,20 @@ func (i *chainInteractor) SalaryDeploy(ctx context.Context, firtsAdmin models.Or return nil } -// func (i *chainInteractor) +type ListMultisigsParams struct { + OrganizationID uuid.UUID +} + +func (i *chainInteractor) ListMultisigs( + ctx context.Context, + params ListMultisigsParams, +) ([]models.Multisig, error) { + multisigs, err := i.txRepository.ListMultisig(ctx, transactions.ListMultisigsParams{ + OrganizationID: params.OrganizationID, + }) + if err != nil { + return nil, fmt.Errorf("error fetch multisigs. %w", err) + } + + return multisigs, nil +} diff --git a/backend/internal/usecase/repository/transactions/repository.go b/backend/internal/usecase/repository/transactions/repository.go index 15443ff..bbe691a 100644 --- a/backend/internal/usecase/repository/transactions/repository.go +++ b/backend/internal/usecase/repository/transactions/repository.go @@ -10,6 +10,7 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/emochka2007/block-accounting/internal/pkg/models" sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils" + "github.com/emochka2007/block-accounting/internal/usecase/repository/organizations" "github.com/google/uuid" ) @@ -52,15 +53,18 @@ type Repository interface { CancelTransaction(ctx context.Context, params CancelTransactionParams) error AddMultisig(ctx context.Context, multisig models.Multisig) error + ListMultisig(ctx context.Context, params ListMultisigsParams) ([]models.Multisig, error) } type repositorySQL struct { - db *sql.DB + db *sql.DB + orgRepo organizations.Repository } -func NewRepository(db *sql.DB) Repository { +func NewRepository(db *sql.DB, orgRepo organizations.Repository) Repository { return &repositorySQL{ - db: db, + db: db, + orgRepo: orgRepo, } } @@ -410,12 +414,16 @@ func (r *repositorySQL) AddMultisig( "id", "organization_id", "title", + "address", + "confirmations", "created_at", "updated_at", ).Values( multisig.ID, multisig.OrganizationID, multisig.Title, + multisig.Address, + multisig.ConfirmationsRequired, multisig.CreatedAt, multisig.UpdatedAt, ).PlaceholderFormat(sq.Dollar) @@ -445,3 +453,141 @@ func (r *repositorySQL) AddMultisig( return nil }) } + +type ListMultisigsParams struct { + IDs uuid.UUIDs + OrganizationID uuid.UUID +} + +func (r *repositorySQL) ListMultisig( + ctx context.Context, + params ListMultisigsParams, +) ([]models.Multisig, error) { + msgs := make([]models.Multisig, 0) + + if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error { + query := sq.Select( + "id", + "organization_id", + "title", + "address", + "confirmations", + "created_at", + "updated_at", + ).From("multisigs").Where(sq.Eq{ + "organization_id": params.OrganizationID, + }).PlaceholderFormat(sq.Dollar) + + rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx) + if err != nil { + return fmt.Errorf("error fetch multisigs from database. %w", err) + } + + defer rows.Close() + + msgsTmp := make([]*models.Multisig, 0) + + for rows.Next() { + var ( + id uuid.UUID + organizationID uuid.UUID + address []byte + title string + confirmations int + createdAt time.Time + updatedAt time.Time + ) + + if err = rows.Scan( + &id, + &organizationID, + &title, + &address, + &confirmations, + &createdAt, + &updatedAt, + ); err != nil { + return fmt.Errorf("error scan row. %w", err) + } + + msgsTmp = append(msgsTmp, &models.Multisig{ + ID: id, + Title: title, + Address: address, + OrganizationID: organizationID, + ConfirmationsRequired: confirmations, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }) + } + + for _, m := range msgsTmp { + owners, err := r.fetchOwners(ctx, fetchOwnersParams{ + OrganizationID: params.OrganizationID, + MultisigID: m.ID, + }) + if err != nil { + return err + } + + m.Owners = owners + + msgs = append(msgs, *m) + } + + return nil + }); err != nil { + return nil, err + } + + return msgs, nil +} + +type fetchOwnersParams struct { + MultisigID uuid.UUID + OrganizationID uuid.UUID +} + +func (r *repositorySQL) fetchOwners(ctx context.Context, params fetchOwnersParams) ([]models.OrganizationParticipant, error) { + owners := make([]models.OrganizationParticipant, 0) + + if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error { + query := sq.Select("owner_id").From("multisig_owners").Where(sq.Eq{ + "multisig_id": params.MultisigID, + }).PlaceholderFormat(sq.Dollar) + + rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx) + if err != nil { + return fmt.Errorf("error fetch multisigs owners from database. %w", err) + } + + defer rows.Close() + + ids := make(uuid.UUIDs, 0) + + for rows.Next() { + var ownerId uuid.UUID + + if err = rows.Scan(&ownerId); err != nil { + return err + } + + ids = append(ids, ownerId) + } + + owners, err = r.orgRepo.Participants(ctx, organizations.ParticipantsParams{ + OrganizationId: params.OrganizationID, + Ids: ids, + UsersOnly: true, + }) + if err != nil { + return fmt.Errorf("error fetch owners as participants. %w", err) + } + + return nil + }); err != nil { + return nil, err + } + + return owners, nil +} diff --git a/backend/migrations/blockd.sql b/backend/migrations/blockd.sql index bd2518e..d434211 100644 --- a/backend/migrations/blockd.sql +++ b/backend/migrations/blockd.sql @@ -159,6 +159,8 @@ create index if not exists idx_contracts_organization_id_created_by create table multisigs ( id uuid primary key, organization_id uuid not null references organizations(id), + address bytea not null, + confirmations smallint default 0, title varchar(350) default 'New Multi-Sig', created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp