mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-01-18 15:36:27 +00:00
new payroll
This commit is contained in:
parent
4b85cdf811
commit
70f66a4ad4
@ -534,7 +534,6 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Deprecated
|
|
||||||
## GET **/{organization_id}/transactions**
|
## GET **/{organization_id}/transactions**
|
||||||
Feth txs
|
Feth txs
|
||||||
### Request body:
|
### Request body:
|
||||||
|
@ -44,11 +44,13 @@ func provideTxInteractor(
|
|||||||
log *slog.Logger,
|
log *slog.Logger,
|
||||||
txRepo txRepo.Repository,
|
txRepo txRepo.Repository,
|
||||||
orgInteractor organizations.OrganizationsInteractor,
|
orgInteractor organizations.OrganizationsInteractor,
|
||||||
|
chainInteractor chain.ChainInteractor,
|
||||||
) transactions.TransactionsInteractor {
|
) transactions.TransactionsInteractor {
|
||||||
return transactions.NewTransactionsInteractor(
|
return transactions.NewTransactionsInteractor(
|
||||||
log.WithGroup("transaction-interactor"),
|
log.WithGroup("transaction-interactor"),
|
||||||
txRepo,
|
txRepo,
|
||||||
orgInteractor,
|
orgInteractor,
|
||||||
|
chainInteractor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
|
|||||||
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository, organizationsInteractor)
|
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository, organizationsInteractor)
|
||||||
organizationsPresenter := provideOrganizationsPresenter()
|
organizationsPresenter := provideOrganizationsPresenter()
|
||||||
organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter)
|
organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter)
|
||||||
transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor)
|
transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor, chainInteractor)
|
||||||
transactionsController := provideTxController(logger, transactionsInteractor, chainInteractor, organizationsInteractor)
|
transactionsController := provideTxController(logger, transactionsInteractor, chainInteractor, organizationsInteractor)
|
||||||
participantsController := provideParticipantsController(logger, organizationsInteractor, usersInteractor)
|
participantsController := provideParticipantsController(logger, organizationsInteractor, usersInteractor)
|
||||||
rootController := provideControllers(logger, authController, organizationsController, transactionsController, participantsController)
|
rootController := provideControllers(logger, authController, organizationsController, transactionsController, participantsController)
|
||||||
|
@ -272,9 +272,72 @@ func (s *transactionsController) ListMultisigs(w http.ResponseWriter, r *http.Re
|
|||||||
return s.txPresenter.ResponseMultisigs(r.Context(), msgs)
|
return s.txPresenter.ResponseMultisigs(r.Context(), msgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo creates a new payout
|
|
||||||
func (c *transactionsController) NewPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
|
func (c *transactionsController) NewPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
|
||||||
return nil, nil
|
req, err := presenters.CreateRequest[domain.NewPayrollRequest](r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error build request. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
organizationID, err := ctxmeta.OrganizationId(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("erropr fetch organization id from context. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.log.Debug(
|
||||||
|
"NewPayrollRequest",
|
||||||
|
slog.Any("req", req),
|
||||||
|
slog.String("org id", organizationID.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
multisigID, err := uuid.Parse(req.MultisigID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error invalid ")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := ctxmeta.User(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetch user from context. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userParticipant, err := c.organizationsInteractor.Participant(ctx, organizations.ParticipantParams{
|
||||||
|
ID: user.Id(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
// TODO fetch REAL first admin
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetch user protocpant. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userParticipant.IsOwner() {
|
||||||
|
return nil, fmt.Errorf("only owner can create payrolls")
|
||||||
|
}
|
||||||
|
|
||||||
|
firstAdmin, err := c.organizationsInteractor.Participant(ctx, organizations.ParticipantParams{
|
||||||
|
ID: user.Id(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
// TODO fetch REAL first admin
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetch first admin. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !firstAdmin.IsOwner() {
|
||||||
|
return nil, fmt.Errorf("invalid first admin. not owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.chainInteractor.PayrollDeploy(ctx, chain.PayrollDeployParams{
|
||||||
|
MultisigID: multisigID,
|
||||||
|
FirstAdmin: firstAdmin,
|
||||||
|
Title: req.Title,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error create new payroll contract. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return presenters.ResponseOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *transactionsController) ConfirmPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
|
func (c *transactionsController) ConfirmPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
|
||||||
|
@ -45,6 +45,10 @@ type LoginResponse struct {
|
|||||||
RTExpiredAt int64 `json:"refresh_token_expired_at"`
|
RTExpiredAt int64 `json:"refresh_token_expired_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NewInviteLinkRequest struct {
|
||||||
|
ExpirationDate int `json:"expiration_date"`
|
||||||
|
}
|
||||||
|
|
||||||
// Organizations
|
// Organizations
|
||||||
|
|
||||||
type NewOrganizationRequest struct {
|
type NewOrganizationRequest struct {
|
||||||
@ -102,6 +106,8 @@ type AddEmployeeRequest struct {
|
|||||||
WalletAddress string `json:"wallet_address"`
|
WalletAddress string `json:"wallet_address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
|
||||||
type NewMultisigRequest struct {
|
type NewMultisigRequest struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Owners []struct {
|
Owners []struct {
|
||||||
@ -110,6 +116,7 @@ type NewMultisigRequest struct {
|
|||||||
Confirmations int `json:"confirmations"`
|
Confirmations int `json:"confirmations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewInviteLinkRequest struct {
|
type NewPayrollRequest struct {
|
||||||
ExpirationDate int `json:"expiration_date"`
|
MultisigID string `json:"multisig_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (s *Server) buildRouter() {
|
|||||||
|
|
||||||
router.Use(render.SetContentType(render.ContentTypeJSON))
|
router.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
|
|
||||||
router.Get("/ping", s.handle(s.controllers.Ping.Ping, "ping"))
|
router.Get("/ping", s.handle(s.controllers.Ping.Ping, "ping")) // DEBUG
|
||||||
|
|
||||||
router.Post("/join", s.handle(s.controllers.Auth.Join, "join"))
|
router.Post("/join", s.handle(s.controllers.Auth.Join, "join"))
|
||||||
router.Post("/login", s.handle(s.controllers.Auth.Login, "login"))
|
router.Post("/login", s.handle(s.controllers.Auth.Login, "login"))
|
||||||
@ -108,16 +108,6 @@ func (s *Server) buildRouter() {
|
|||||||
r.Post("/", s.handle(s.controllers.Organizations.NewOrganization, "new_organization"))
|
r.Post("/", s.handle(s.controllers.Organizations.NewOrganization, "new_organization"))
|
||||||
|
|
||||||
r.Route("/{organization_id}", func(r chi.Router) {
|
r.Route("/{organization_id}", func(r chi.Router) {
|
||||||
// Deprecated??
|
|
||||||
r.Route("/transactions", func(r chi.Router) {
|
|
||||||
r.Get("/", s.handle(s.controllers.Transactions.List, "tx_list"))
|
|
||||||
r.Post("/", s.handle(s.controllers.Transactions.New, "new_tx"))
|
|
||||||
r.Put(
|
|
||||||
"/{tx_id}",
|
|
||||||
s.handle(s.controllers.Transactions.UpdateStatus, "update_tx_status"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Route("/payrolls", func(r chi.Router) {
|
r.Route("/payrolls", func(r chi.Router) {
|
||||||
r.Get("/", s.handle(s.controllers.Transactions.ListPayrolls, "list_payrolls"))
|
r.Get("/", s.handle(s.controllers.Transactions.ListPayrolls, "list_payrolls"))
|
||||||
r.Post("/", s.handle(s.controllers.Transactions.NewPayroll, "new_payroll"))
|
r.Post("/", s.handle(s.controllers.Transactions.NewPayroll, "new_payroll"))
|
||||||
@ -144,6 +134,16 @@ func (s *Server) buildRouter() {
|
|||||||
r.Get("/", nil) // todo если успею
|
r.Get("/", nil) // todo если успею
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Deprecated??
|
||||||
|
r.Route("/transactions", func(r chi.Router) {
|
||||||
|
r.Get("/", s.handle(s.controllers.Transactions.List, "tx_list"))
|
||||||
|
r.Post("/", s.handle(s.controllers.Transactions.New, "new_tx"))
|
||||||
|
r.Put(
|
||||||
|
"/{tx_id}",
|
||||||
|
s.handle(s.controllers.Transactions.UpdateStatus, "update_tx_status"),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -23,3 +23,11 @@ type MultisigConfirmation struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Payroll struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Title string
|
||||||
|
Address []byte
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
MultisigID uuid.UUID
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
"github.com/emochka2007/block-accounting/internal/pkg/config"
|
||||||
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/logger"
|
||||||
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||||
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
|
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -19,10 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ChainInteractor interface {
|
type ChainInteractor interface {
|
||||||
|
PubKey(ctx context.Context, user *models.User) ([]byte, error)
|
||||||
|
|
||||||
NewMultisig(ctx context.Context, params NewMultisigParams) error
|
NewMultisig(ctx context.Context, params NewMultisigParams) error
|
||||||
ListMultisigs(ctx context.Context, params ListMultisigsParams) ([]models.Multisig, 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
|
PayrollDeploy(ctx context.Context, params PayrollDeployParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type chainInteractor struct {
|
type chainInteractor struct {
|
||||||
@ -90,70 +93,138 @@ func (i *chainInteractor) NewMultisig(ctx context.Context, params NewMultisigPar
|
|||||||
return fmt.Errorf("error fetch organization id from context. %w", err)
|
return fmt.Errorf("error fetch organization id from context. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := bytes.NewBuffer(requestBody)
|
go func() {
|
||||||
|
pid := uuid.Must(uuid.NewV7()).String()
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
i.log.Info(
|
||||||
if err != nil {
|
"new multisig worker started",
|
||||||
return fmt.Errorf("error build request. %w", err)
|
slog.String("pid", pid),
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error(
|
|
||||||
"error send deploy multisig request",
|
|
||||||
slog.String("endpoint", endpoint),
|
|
||||||
slog.Any("params", params),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return fmt.Errorf("error build new multisig request. %w", err)
|
doneCh := make(chan struct{})
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
i.log.Error("worker paniced!", slog.Any("panic", err))
|
||||||
|
}
|
||||||
|
|
||||||
raw, err := io.ReadAll(resp.Body)
|
doneCh <- struct{}{}
|
||||||
if err != nil {
|
close(doneCh)
|
||||||
return fmt.Errorf("error read body. %w", err)
|
}()
|
||||||
}
|
|
||||||
|
|
||||||
respObject := new(newMultisigChainResponse)
|
go func() {
|
||||||
|
warn := time.After(1 * time.Minute)
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
i.log.Info(
|
||||||
|
"new multisig worker done",
|
||||||
|
slog.String("pid", pid),
|
||||||
|
slog.Time("started at", startTime),
|
||||||
|
slog.Time("done at", time.Now()),
|
||||||
|
slog.Duration("work time", time.Since(startTime)),
|
||||||
|
)
|
||||||
|
case <-warn:
|
||||||
|
i.log.Warn(
|
||||||
|
"new multisig worker seems sleeping",
|
||||||
|
slog.String("pid", pid),
|
||||||
|
slog.Duration("work time", time.Since(startTime)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &respObject); err != nil {
|
requestContext, cancel := context.WithTimeout(context.TODO(), time.Minute*15)
|
||||||
return fmt.Errorf("error parse chain-api response body. %w", err)
|
defer cancel()
|
||||||
}
|
|
||||||
|
|
||||||
if respObject.Address == "" {
|
body := bytes.NewBuffer(requestBody)
|
||||||
return fmt.Errorf("error multisig address is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
multisigAddress := common.Hex2Bytes(respObject.Address[2:])
|
req, err := http.NewRequestWithContext(requestContext, http.MethodPost, endpoint, body)
|
||||||
|
if err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error build request",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
createdAt := time.Now()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
msg := models.Multisig{
|
req.Header.Add("Content-Type", "application/json")
|
||||||
ID: uuid.Must(uuid.NewV7()),
|
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
||||||
Title: params.Title,
|
|
||||||
Address: multisigAddress,
|
|
||||||
OrganizationID: organizationID,
|
|
||||||
Owners: params.Owners,
|
|
||||||
ConfirmationsRequired: params.Confirmations,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: createdAt,
|
|
||||||
}
|
|
||||||
|
|
||||||
i.log.Debug(
|
resp, err := http.DefaultClient.Do(req)
|
||||||
"deploy multisig response",
|
if err != nil {
|
||||||
slog.Int("code", resp.StatusCode),
|
i.log.Error(
|
||||||
slog.String("body", string(raw)),
|
"error send deploy multisig request",
|
||||||
slog.Any("parsed", respObject),
|
slog.String("endpoint", endpoint),
|
||||||
slog.Any("multisig object", msg),
|
slog.Any("params", params),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := i.txRepository.AddMultisig(ctx, msg); err != nil {
|
return
|
||||||
return fmt.Errorf("error add new multisig. %w", err)
|
}
|
||||||
}
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
raw, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error read body",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respObject := new(newMultisigChainResponse)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(raw, &respObject); err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error parse chain-api response body",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if respObject.Address == "" {
|
||||||
|
i.log.Error(
|
||||||
|
"error multisig address is empty",
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
multisigAddress := common.Hex2Bytes(respObject.Address[2:])
|
||||||
|
|
||||||
|
createdAt := time.Now()
|
||||||
|
|
||||||
|
msg := models.Multisig{
|
||||||
|
ID: uuid.Must(uuid.NewV7()),
|
||||||
|
Title: params.Title,
|
||||||
|
Address: multisigAddress,
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
Owners: params.Owners,
|
||||||
|
ConfirmationsRequired: params.Confirmations,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: createdAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
i.log.Debug(
|
||||||
|
"deploy multisig response",
|
||||||
|
slog.Int("code", resp.StatusCode),
|
||||||
|
slog.String("body", string(raw)),
|
||||||
|
slog.Any("parsed", respObject),
|
||||||
|
slog.Any("multisig object", msg),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := i.txRepository.AddMultisig(requestContext, msg); err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error add new multisig",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -194,46 +265,203 @@ func (i *chainInteractor) PubKey(ctx context.Context, user *models.User) ([]byte
|
|||||||
return common.Hex2Bytes(pubKeyStr), nil
|
return common.Hex2Bytes(pubKeyStr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *chainInteractor) SalaryDeploy(ctx context.Context, firtsAdmin models.OrganizationParticipant) error {
|
type PayrollDeployParams struct {
|
||||||
|
FirstAdmin models.OrganizationParticipant
|
||||||
|
MultisigID uuid.UUID
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
type newPayrollContractChainResponse struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *chainInteractor) PayrollDeploy(
|
||||||
|
ctx context.Context,
|
||||||
|
params PayrollDeployParams,
|
||||||
|
) error {
|
||||||
user, err := ctxmeta.User(ctx)
|
user, err := ctxmeta.User(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error fetch user from context. %w", err)
|
return fmt.Errorf("error fetch user from context. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Id() != firtsAdmin.Id() || firtsAdmin.GetUser() == nil {
|
if user.Id() != params.FirstAdmin.Id() || params.FirstAdmin.GetUser() == nil {
|
||||||
return fmt.Errorf("error unauthorized access")
|
return fmt.Errorf("error unauthorized access")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organizationID, err := ctxmeta.OrganizationId(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetch organization id from context. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
multisigs, err := i.ListMultisigs(ctx, ListMultisigsParams{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
IDs: uuid.UUIDs{params.MultisigID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetch multisigs by id. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(multisigs) == 0 {
|
||||||
|
return fmt.Errorf("error empty multisigs set")
|
||||||
|
}
|
||||||
|
|
||||||
|
i.log.Debug(
|
||||||
|
"PayrollDeploy",
|
||||||
|
slog.String("organization id", organizationID.String()),
|
||||||
|
slog.String("multisig id", params.MultisigID.String()),
|
||||||
|
slog.String("multisig address", common.Bytes2Hex(multisigs[0].Address)),
|
||||||
|
slog.String("X-Seed header data", common.Bytes2Hex(user.Seed())),
|
||||||
|
)
|
||||||
|
|
||||||
|
maddr := common.Bytes2Hex(multisigs[0].Address)
|
||||||
|
|
||||||
|
if maddr == "" {
|
||||||
|
return fmt.Errorf("empty multisig address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maddr[0] != 0 && maddr[1] != 'x' {
|
||||||
|
maddr = "0x" + maddr
|
||||||
|
}
|
||||||
|
|
||||||
requestBody, err := json.Marshal(map[string]any{
|
requestBody, err := json.Marshal(map[string]any{
|
||||||
"authorizedWallet": common.Bytes2Hex(user.Seed()),
|
"authorizedWallet": maddr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error marshal request body. %w", err)
|
return fmt.Errorf("error marshal request body. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := bytes.NewBuffer(requestBody)
|
go func() {
|
||||||
|
pid := uuid.Must(uuid.NewV7()).String()
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
endpoint := i.config.ChainAPI.Host + "/salaries/deploy"
|
i.log.Info(
|
||||||
|
"new paroll worker started",
|
||||||
|
slog.String("pid", pid),
|
||||||
|
)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
doneCh := make(chan struct{})
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error build request. %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
defer func() {
|
||||||
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
if err := recover(); err != nil {
|
||||||
|
i.log.Error("worker paniced!", slog.Any("panic", err))
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
doneCh <- struct{}{}
|
||||||
if err != nil {
|
close(doneCh)
|
||||||
return fmt.Errorf("error fetch deploy salary contract. %w", err)
|
}()
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
go func() {
|
||||||
|
warn := time.After(2 * time.Minute)
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
i.log.Info(
|
||||||
|
"new paroll worker done",
|
||||||
|
slog.String("pid", pid),
|
||||||
|
slog.Time("started at", startTime),
|
||||||
|
slog.Time("done at", time.Now()),
|
||||||
|
slog.Duration("work time", time.Since(startTime)),
|
||||||
|
)
|
||||||
|
case <-warn:
|
||||||
|
i.log.Warn(
|
||||||
|
"new paroll worker seems sleeping",
|
||||||
|
slog.String("pid", pid),
|
||||||
|
slog.Duration("work time", time.Since(startTime)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
requestContext, cancel := context.WithTimeout(context.TODO(), time.Minute*20)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
body := bytes.NewBuffer(requestBody)
|
||||||
|
|
||||||
|
endpoint := i.config.ChainAPI.Host + "/salaries/deploy"
|
||||||
|
|
||||||
|
i.log.Debug(
|
||||||
|
"request",
|
||||||
|
slog.String("body", string(requestBody)),
|
||||||
|
slog.String("endpoint", endpoint),
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(requestContext, http.MethodPost, endpoint, body)
|
||||||
|
if err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error build request",
|
||||||
|
logger.Err(fmt.Errorf("error build request. %w", err)),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error fetch deploy salary contract",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
raw, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error read body",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respObject := new(newPayrollContractChainResponse)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(raw, &respObject); err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error parse chain-api response body",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if respObject.Address == "" {
|
||||||
|
i.log.Error(
|
||||||
|
"error multisig address is empty",
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := common.Hex2Bytes(respObject.Address[2:])
|
||||||
|
|
||||||
|
createdAt := time.Now()
|
||||||
|
|
||||||
|
if err := i.txRepository.AddPayrollContract(requestContext, transactions.AddPayrollContract{
|
||||||
|
ID: uuid.Must(uuid.NewV7()),
|
||||||
|
Title: params.Title,
|
||||||
|
Address: addr,
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
MultisigID: params.MultisigID,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
}); err != nil {
|
||||||
|
i.log.Error(
|
||||||
|
"error add new payroll contract",
|
||||||
|
logger.Err(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListMultisigsParams struct {
|
type ListMultisigsParams struct {
|
||||||
|
IDs uuid.UUIDs
|
||||||
OrganizationID uuid.UUID
|
OrganizationID uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +470,7 @@ func (i *chainInteractor) ListMultisigs(
|
|||||||
params ListMultisigsParams,
|
params ListMultisigsParams,
|
||||||
) ([]models.Multisig, error) {
|
) ([]models.Multisig, error) {
|
||||||
multisigs, err := i.txRepository.ListMultisig(ctx, transactions.ListMultisigsParams{
|
multisigs, err := i.txRepository.ListMultisig(ctx, transactions.ListMultisigsParams{
|
||||||
|
IDs: params.IDs,
|
||||||
OrganizationID: params.OrganizationID,
|
OrganizationID: params.OrganizationID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,6 +44,7 @@ type ParticipantParams struct {
|
|||||||
UsersOnly bool
|
UsersOnly bool
|
||||||
ActiveOnly bool
|
ActiveOnly bool
|
||||||
EmployeesOnly bool
|
EmployeesOnly bool
|
||||||
|
OwnerOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParticipantsParams struct {
|
type ParticipantsParams struct {
|
||||||
@ -54,6 +55,7 @@ type ParticipantsParams struct {
|
|||||||
UsersOnly bool
|
UsersOnly bool
|
||||||
ActiveOnly bool
|
ActiveOnly bool
|
||||||
EmployeesOnly bool
|
EmployeesOnly bool
|
||||||
|
OwnerOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrganizationsInteractor interface {
|
type OrganizationsInteractor interface {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
||||||
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/usecase/interactors/chain"
|
||||||
"github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations"
|
"github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations"
|
||||||
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
|
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -59,20 +60,23 @@ type TransactionsInteractor interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type transactionsInteractor struct {
|
type transactionsInteractor struct {
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
txRepo transactions.Repository
|
txRepo transactions.Repository
|
||||||
orgInteractor organizations.OrganizationsInteractor
|
orgInteractor organizations.OrganizationsInteractor
|
||||||
|
chainInteractor chain.ChainInteractor
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransactionsInteractor(
|
func NewTransactionsInteractor(
|
||||||
log *slog.Logger,
|
log *slog.Logger,
|
||||||
txRepo transactions.Repository,
|
txRepo transactions.Repository,
|
||||||
orgInteractor organizations.OrganizationsInteractor,
|
orgInteractor organizations.OrganizationsInteractor,
|
||||||
|
chainInteractor chain.ChainInteractor,
|
||||||
) TransactionsInteractor {
|
) TransactionsInteractor {
|
||||||
return &transactionsInteractor{
|
return &transactionsInteractor{
|
||||||
log: log,
|
log: log,
|
||||||
txRepo: txRepo,
|
txRepo: txRepo,
|
||||||
orgInteractor: orgInteractor,
|
orgInteractor: orgInteractor,
|
||||||
|
chainInteractor: chainInteractor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +228,8 @@ func (i *transactionsInteractor) Confirm(ctx context.Context, params ConfirmPara
|
|||||||
return nil, fmt.Errorf("error confirm transaction. %w", err)
|
return nil, fmt.Errorf("error confirm transaction. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO confirm tx via chain-api
|
||||||
|
|
||||||
tx, err := i.txRepo.GetTransactions(ctx, transactions.GetTransactionsParams{
|
tx, err := i.txRepo.GetTransactions(ctx, transactions.GetTransactionsParams{
|
||||||
Ids: uuid.UUIDs{params.TxID},
|
Ids: uuid.UUIDs{params.TxID},
|
||||||
OrganizationId: params.OrganizationID,
|
OrganizationId: params.OrganizationID,
|
||||||
@ -240,6 +246,8 @@ func (i *transactionsInteractor) Confirm(ctx context.Context, params ConfirmPara
|
|||||||
return tx[0], nil
|
return tx[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Execute()
|
||||||
|
|
||||||
func (i *transactionsInteractor) Cancel(ctx context.Context, params CancelParams) (*models.Transaction, error) {
|
func (i *transactionsInteractor) Cancel(ctx context.Context, params CancelParams) (*models.Transaction, error) {
|
||||||
user, err := ctxmeta.User(ctx)
|
user, err := ctxmeta.User(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -54,6 +54,9 @@ type Repository interface {
|
|||||||
|
|
||||||
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)
|
ListMultisig(ctx context.Context, params ListMultisigsParams) ([]models.Multisig, error)
|
||||||
|
ConfirmMultisig(ctx context.Context, params ConfirmMultisigParams) error
|
||||||
|
|
||||||
|
AddPayrollContract(ctx context.Context, params AddPayrollContract) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type repositorySQL struct {
|
type repositorySQL struct {
|
||||||
@ -478,6 +481,12 @@ func (r *repositorySQL) ListMultisig(
|
|||||||
"organization_id": params.OrganizationID,
|
"organization_id": params.OrganizationID,
|
||||||
}).PlaceholderFormat(sq.Dollar)
|
}).PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
|
if len(params.IDs) > 0 {
|
||||||
|
query = query.Where(sq.Eq{
|
||||||
|
"id": params.IDs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
|
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error fetch multisigs from database. %w", err)
|
return fmt.Errorf("error fetch multisigs from database. %w", err)
|
||||||
@ -543,6 +552,94 @@ func (r *repositorySQL) ListMultisig(
|
|||||||
return msgs, nil
|
return msgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfirmMultisigParams struct {
|
||||||
|
MultisigID uuid.UUID
|
||||||
|
OrganizationsID uuid.UUID
|
||||||
|
CinfirmedBy *models.OrganizationUser
|
||||||
|
ConfirmedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) ConfirmMultisig(ctx context.Context, params ConfirmMultisigParams) error {
|
||||||
|
return sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
|
||||||
|
deleteOldQuery := sq.Delete("multisig_confirmations").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"multisig_id": params.MultisigID,
|
||||||
|
"owner_id": params.CinfirmedBy.Id(),
|
||||||
|
}).
|
||||||
|
PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
|
if _, err := deleteOldQuery.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
|
||||||
|
return fmt.Errorf("error delete old multisig confirmation. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := sq.Insert("multisig_confirmations").
|
||||||
|
Columns(
|
||||||
|
"multisig_id",
|
||||||
|
"owner_id",
|
||||||
|
"created_at",
|
||||||
|
).
|
||||||
|
Values(
|
||||||
|
params.MultisigID,
|
||||||
|
params.CinfirmedBy.Id(),
|
||||||
|
params.ConfirmedAt,
|
||||||
|
).
|
||||||
|
PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
|
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
|
||||||
|
return fmt.Errorf("error add multisig confirmation. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddPayrollContract struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Address []byte
|
||||||
|
Payload []byte
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
MultisigID uuid.UUID
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) AddPayrollContract(
|
||||||
|
ctx context.Context,
|
||||||
|
params AddPayrollContract,
|
||||||
|
) error {
|
||||||
|
return sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
|
||||||
|
query := sq.Insert("payrolls").
|
||||||
|
Columns(
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"address",
|
||||||
|
"payload",
|
||||||
|
"organization_id",
|
||||||
|
"multisig_id",
|
||||||
|
"created_at",
|
||||||
|
).
|
||||||
|
Values(
|
||||||
|
params.ID,
|
||||||
|
params.Title,
|
||||||
|
params.Description,
|
||||||
|
params.Address,
|
||||||
|
params.Payload,
|
||||||
|
params.OrganizationID,
|
||||||
|
params.MultisigID,
|
||||||
|
params.CreatedAt,
|
||||||
|
).
|
||||||
|
PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
|
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
|
||||||
|
return fmt.Errorf("error add new payroll contract. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type fetchOwnersParams struct {
|
type fetchOwnersParams struct {
|
||||||
MultisigID uuid.UUID
|
MultisigID uuid.UUID
|
||||||
OrganizationID uuid.UUID
|
OrganizationID uuid.UUID
|
||||||
|
@ -99,7 +99,7 @@ create table if not exists transactions (
|
|||||||
amount decimal default 0,
|
amount decimal default 0,
|
||||||
|
|
||||||
to_addr bytea not null,
|
to_addr bytea not null,
|
||||||
tx_index bytea default null,
|
tx_index bigint default 0,
|
||||||
|
|
||||||
max_fee_allowed decimal default 0,
|
max_fee_allowed decimal default 0,
|
||||||
deadline timestamp default null,
|
deadline timestamp default null,
|
||||||
@ -193,6 +193,9 @@ create index if not exists idx_multisig_confirmations_owners_multisig_id
|
|||||||
create index if not exists idx_multisig_confirmations_owners_owner_id
|
create index if not exists idx_multisig_confirmations_owners_owner_id
|
||||||
on multisig_confirmations (owner_id);
|
on multisig_confirmations (owner_id);
|
||||||
|
|
||||||
|
create index if not exists idx_multisig_confirmations_owners_multisig_id_owner_id
|
||||||
|
on multisig_confirmations (multisig_id, owner_id);
|
||||||
|
|
||||||
create table invites (
|
create table invites (
|
||||||
link_hash varchar(64) primary key,
|
link_hash varchar(64) primary key,
|
||||||
organization_id uuid,
|
organization_id uuid,
|
||||||
@ -201,3 +204,18 @@ create table invites (
|
|||||||
expired_at timestamp default null,
|
expired_at timestamp default null,
|
||||||
used_at timestamp default null
|
used_at timestamp default null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table payrolls (
|
||||||
|
id uuid primary key,
|
||||||
|
title varchar(250) default 'New Payroll',
|
||||||
|
description text not null,
|
||||||
|
address bytea not null,
|
||||||
|
payload bytea default null,
|
||||||
|
organization_id uuid not null references organizations(id),
|
||||||
|
tx_index bytea default null,
|
||||||
|
multisig_id uuid references multisigs(id),
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
create table payrolls
|
Loading…
Reference in New Issue
Block a user