msigs fetch implemented

This commit is contained in:
r8zavetr8v 2024-05-26 21:20:14 +03:00
parent 0558ddbd04
commit 55c6c14630
9 changed files with 300 additions and 21 deletions

View File

@ -406,16 +406,58 @@ curl --request GET \
Response: Response:
``` json ``` 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** ## POST **/organizations/{organization_id}/payrolls**
New payroll New payroll
## GET **/organizations/{organization_id}/payrolls**
Fetch payrolls
## GET **/organizations/{organization_id}/license** ## GET **/organizations/{organization_id}/license**
Fetch licenses Fetch licenses

View File

@ -24,8 +24,8 @@ func provideOrganizationsRepository(
return organizations.NewRepository(db, uRepo) return organizations.NewRepository(db, uRepo)
} }
func provideTxRepository(db *sql.DB) transactions.Repository { func provideTxRepository(db *sql.DB, or organizations.Repository) transactions.Repository {
return transactions.NewRepository(db) return transactions.NewRepository(db, or)
} }
func provideAuthRepository(db *sql.DB) auth.Repository { func provideAuthRepository(db *sql.DB) auth.Repository {

View File

@ -21,14 +21,14 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
return nil, nil, err return nil, nil, err
} }
usersRepository := provideUsersRepository(db) usersRepository := provideUsersRepository(db)
transactionsRepository := provideTxRepository(db) organizationsRepository := provideOrganizationsRepository(db, usersRepository)
transactionsRepository := provideTxRepository(db, organizationsRepository)
chainInteractor := provideChainInteractor(logger, c, transactionsRepository) chainInteractor := provideChainInteractor(logger, c, transactionsRepository)
usersInteractor := provideUsersInteractor(logger, usersRepository, chainInteractor) usersInteractor := provideUsersInteractor(logger, usersRepository, chainInteractor)
authRepository := provideAuthRepository(db) authRepository := provideAuthRepository(db)
jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository) jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository)
authPresenter := provideAuthPresenter(jwtInteractor) authPresenter := provideAuthPresenter(jwtInteractor)
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository) authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository)
organizationsRepository := provideOrganizationsRepository(db, usersRepository)
client, cleanup2 := provideRedisConnection(c) client, cleanup2 := provideRedisConnection(c)
cache := provideRedisCache(client, logger) cache := provideRedisCache(client, logger)
organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache) organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache)

View File

@ -222,7 +222,7 @@ func (c *transactionsController) NewMultisig(w http.ResponseWriter, r *http.Requ
slog.Any("req", req), slog.Any("req", req),
) )
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel() defer cancel()
ownersPKs := make([][]byte, len(req.Owners)) 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{ if err := c.chainInteractor.NewMultisig(ctx, chain.NewMultisigParams{
Title: req.Title,
Owners: participants, Owners: participants,
Confirmations: req.Confirmations, Confirmations: req.Confirmations,
}); err != nil { }); 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) { 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 // todo creates a new payout

View File

@ -20,6 +20,14 @@ type ParticipantsPresenter interface {
ctx context.Context, ctx context.Context,
participant models.OrganizationParticipant, participant models.OrganizationParticipant,
) ([]byte, error) ) ([]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{} type participantsPresenter struct{}
@ -28,7 +36,7 @@ func NewParticipantsPresenter() ParticipantsPresenter {
return new(participantsPresenter) return new(participantsPresenter)
} }
func (p *participantsPresenter) responseParticipant( func (p *participantsPresenter) ResponseParticipantHal(
ctx context.Context, ctx context.Context,
participant models.OrganizationParticipant, participant models.OrganizationParticipant,
) (*hal.Resource, error) { ) (*hal.Resource, error) {
@ -78,14 +86,14 @@ func (p *participantsPresenter) responseParticipant(
return r, nil return r, nil
} }
func (p *participantsPresenter) responseParticipants( func (p *participantsPresenter) ResponseParticipantsHal(
ctx context.Context, ctx context.Context,
participants []models.OrganizationParticipant, participants []models.OrganizationParticipant,
) (*hal.Resource, error) { ) (*hal.Resource, error) {
resources := make([]*hal.Resource, len(participants)) resources := make([]*hal.Resource, len(participants))
for i, pt := range participants { for i, pt := range participants {
r, err := p.responseParticipant(ctx, pt) r, err := p.ResponseParticipantHal(ctx, pt)
if err != nil { if err != nil {
return nil, fmt.Errorf("error map participant to hal resource. %w", err) return nil, fmt.Errorf("error map participant to hal resource. %w", err)
} }
@ -113,7 +121,7 @@ func (p *participantsPresenter) ResponseListParticipants(
ctx context.Context, ctx context.Context,
participants []models.OrganizationParticipant, participants []models.OrganizationParticipant,
) ([]byte, error) { ) ([]byte, error) {
r, err := p.responseParticipants(ctx, participants) r, err := p.ResponseParticipantsHal(ctx, participants)
if err != nil { if err != nil {
return nil, fmt.Errorf("error map participants to hal. %w", err) return nil, fmt.Errorf("error map participants to hal. %w", err)
} }
@ -130,7 +138,7 @@ func (p *participantsPresenter) ResponseParticipant(
ctx context.Context, ctx context.Context,
participant models.OrganizationParticipant, participant models.OrganizationParticipant,
) ([]byte, error) { ) ([]byte, error) {
r, err := p.responseParticipant(ctx, participant) r, err := p.ResponseParticipantHal(ctx, participant)
if err != nil { if err != nil {
return nil, fmt.Errorf("error map participant to hal resource. %w", err) return nil, fmt.Errorf("error map participant to hal resource. %w", err)
} }

View File

@ -24,13 +24,18 @@ type TransactionsPresenter interface {
ResponseNewTransaction(ctx context.Context, tx *models.Transaction) ([]byte, error) ResponseNewTransaction(ctx context.Context, tx *models.Transaction) ([]byte, error)
ResponseTransactionsArray(ctx context.Context, txs []*models.Transaction) ([]*hal.Resource, error) ResponseTransactionsArray(ctx context.Context, txs []*models.Transaction) ([]*hal.Resource, error)
ResponseListTransactions(ctx context.Context, txs []*models.Transaction, cursor string) ([]byte, error) ResponseListTransactions(ctx context.Context, txs []*models.Transaction, cursor string) ([]byte, error)
ResponseMultisigs(ctx context.Context, msgs []models.Multisig) ([]byte, error)
} }
type transactionsPresenter struct { type transactionsPresenter struct {
participantsPresenter ParticipantsPresenter
} }
func NewTransactionsPresenter() TransactionsPresenter { func NewTransactionsPresenter() TransactionsPresenter {
return &transactionsPresenter{} return &transactionsPresenter{
participantsPresenter: NewParticipantsPresenter(),
}
} }
// RequestTransaction returns a Transaction model WITHOUT CreatedBy user set. CreatedAt set as time.Now() // 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( r := hal.NewResource(
txsResource, txsResource,
"/organizations/"+organizationID.String()+"/transactions", "/organizations/"+organizationID.String()+"/transactions",
hal.WithType("organizations"), hal.WithType("transactions"),
) )
out, err := json.Marshal(r) out, err := json.Marshal(r)
@ -177,3 +182,49 @@ func (c *transactionsPresenter) ResponseNewTransaction(
return out, nil 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
}

View File

@ -20,6 +20,7 @@ import (
type ChainInteractor interface { type ChainInteractor interface {
NewMultisig(ctx context.Context, params NewMultisigParams) error 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) PubKey(ctx context.Context, user *models.User) ([]byte, error)
SalaryDeploy(ctx context.Context, firtsAdmin models.OrganizationParticipant) 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 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
}

View File

@ -10,6 +10,7 @@ import (
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/emochka2007/block-accounting/internal/pkg/models" "github.com/emochka2007/block-accounting/internal/pkg/models"
sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils" sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils"
"github.com/emochka2007/block-accounting/internal/usecase/repository/organizations"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -52,15 +53,18 @@ type Repository interface {
CancelTransaction(ctx context.Context, params CancelTransactionParams) error CancelTransaction(ctx context.Context, params CancelTransactionParams) error
AddMultisig(ctx context.Context, multisig models.Multisig) error AddMultisig(ctx context.Context, multisig models.Multisig) error
ListMultisig(ctx context.Context, params ListMultisigsParams) ([]models.Multisig, error)
} }
type repositorySQL struct { 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{ return &repositorySQL{
db: db, db: db,
orgRepo: orgRepo,
} }
} }
@ -410,12 +414,16 @@ func (r *repositorySQL) AddMultisig(
"id", "id",
"organization_id", "organization_id",
"title", "title",
"address",
"confirmations",
"created_at", "created_at",
"updated_at", "updated_at",
).Values( ).Values(
multisig.ID, multisig.ID,
multisig.OrganizationID, multisig.OrganizationID,
multisig.Title, multisig.Title,
multisig.Address,
multisig.ConfirmationsRequired,
multisig.CreatedAt, multisig.CreatedAt,
multisig.UpdatedAt, multisig.UpdatedAt,
).PlaceholderFormat(sq.Dollar) ).PlaceholderFormat(sq.Dollar)
@ -445,3 +453,141 @@ func (r *repositorySQL) AddMultisig(
return nil 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
}

View File

@ -159,6 +159,8 @@ create index if not exists idx_contracts_organization_id_created_by
create table multisigs ( create table multisigs (
id uuid primary key, id uuid primary key,
organization_id uuid not null references organizations(id), organization_id uuid not null references organizations(id),
address bytea not null,
confirmations smallint default 0,
title varchar(350) default 'New Multi-Sig', title varchar(350) default 'New Multi-Sig',
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp updated_at timestamp default current_timestamp