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**
|
||||
Feth txs
|
||||
### Request body:
|
||||
|
@ -44,11 +44,13 @@ func provideTxInteractor(
|
||||
log *slog.Logger,
|
||||
txRepo txRepo.Repository,
|
||||
orgInteractor organizations.OrganizationsInteractor,
|
||||
chainInteractor chain.ChainInteractor,
|
||||
) transactions.TransactionsInteractor {
|
||||
return transactions.NewTransactionsInteractor(
|
||||
log.WithGroup("transaction-interactor"),
|
||||
txRepo,
|
||||
orgInteractor,
|
||||
chainInteractor,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
|
||||
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor, authRepository, organizationsInteractor)
|
||||
organizationsPresenter := provideOrganizationsPresenter()
|
||||
organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter)
|
||||
transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor)
|
||||
transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor, chainInteractor)
|
||||
transactionsController := provideTxController(logger, transactionsInteractor, chainInteractor, organizationsInteractor)
|
||||
participantsController := provideParticipantsController(logger, organizationsInteractor, usersInteractor)
|
||||
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)
|
||||
}
|
||||
|
||||
// todo creates a new payout
|
||||
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) {
|
||||
|
@ -45,6 +45,10 @@ type LoginResponse struct {
|
||||
RTExpiredAt int64 `json:"refresh_token_expired_at"`
|
||||
}
|
||||
|
||||
type NewInviteLinkRequest struct {
|
||||
ExpirationDate int `json:"expiration_date"`
|
||||
}
|
||||
|
||||
// Organizations
|
||||
|
||||
type NewOrganizationRequest struct {
|
||||
@ -102,6 +106,8 @@ type AddEmployeeRequest struct {
|
||||
WalletAddress string `json:"wallet_address"`
|
||||
}
|
||||
|
||||
// Chain
|
||||
|
||||
type NewMultisigRequest struct {
|
||||
Title string `json:"title"`
|
||||
Owners []struct {
|
||||
@ -110,6 +116,7 @@ type NewMultisigRequest struct {
|
||||
Confirmations int `json:"confirmations"`
|
||||
}
|
||||
|
||||
type NewInviteLinkRequest struct {
|
||||
ExpirationDate int `json:"expiration_date"`
|
||||
type NewPayrollRequest struct {
|
||||
MultisigID string `json:"multisig_id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (s *Server) buildRouter() {
|
||||
|
||||
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("/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.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.Get("/", s.handle(s.controllers.Transactions.ListPayrolls, "list_payrolls"))
|
||||
r.Post("/", s.handle(s.controllers.Transactions.NewPayroll, "new_payroll"))
|
||||
@ -144,6 +134,16 @@ func (s *Server) buildRouter() {
|
||||
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
|
||||
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/ctxmeta"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/logger"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -19,10 +20,12 @@ import (
|
||||
)
|
||||
|
||||
type ChainInteractor interface {
|
||||
PubKey(ctx context.Context, user *models.User) ([]byte, 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)
|
||||
SalaryDeploy(ctx context.Context, firtsAdmin models.OrganizationParticipant) error
|
||||
|
||||
PayrollDeploy(ctx context.Context, params PayrollDeployParams) error
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
body := bytes.NewBuffer(requestBody)
|
||||
go func() {
|
||||
pid := uuid.Must(uuid.NewV7()).String()
|
||||
startTime := time.Now()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error build request. %w", err)
|
||||
}
|
||||
|
||||
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),
|
||||
i.log.Info(
|
||||
"new multisig worker started",
|
||||
slog.String("pid", pid),
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error read body. %w", err)
|
||||
}
|
||||
doneCh <- struct{}{}
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("error parse chain-api response body. %w", err)
|
||||
}
|
||||
requestContext, cancel := context.WithTimeout(context.TODO(), time.Minute*15)
|
||||
defer cancel()
|
||||
|
||||
if respObject.Address == "" {
|
||||
return fmt.Errorf("error multisig address is empty")
|
||||
}
|
||||
body := bytes.NewBuffer(requestBody)
|
||||
|
||||
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{
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Title: params.Title,
|
||||
Address: multisigAddress,
|
||||
OrganizationID: organizationID,
|
||||
Owners: params.Owners,
|
||||
ConfirmationsRequired: params.Confirmations,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: createdAt,
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
||||
|
||||
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),
|
||||
)
|
||||
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),
|
||||
)
|
||||
|
||||
if err := i.txRepository.AddMultisig(ctx, msg); err != nil {
|
||||
return fmt.Errorf("error add new multisig. %w", 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(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
|
||||
}
|
||||
@ -194,46 +265,203 @@ func (i *chainInteractor) PubKey(ctx context.Context, user *models.User) ([]byte
|
||||
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)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
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{
|
||||
"authorizedWallet": common.Bytes2Hex(user.Seed()),
|
||||
"authorizedWallet": maddr,
|
||||
})
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error build request. %w", err)
|
||||
}
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("X-Seed", common.Bytes2Hex(user.Seed()))
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
i.log.Error("worker paniced!", slog.Any("panic", err))
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetch deploy salary contract. %w", err)
|
||||
}
|
||||
doneCh <- struct{}{}
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type ListMultisigsParams struct {
|
||||
IDs uuid.UUIDs
|
||||
OrganizationID uuid.UUID
|
||||
}
|
||||
|
||||
@ -242,6 +470,7 @@ func (i *chainInteractor) ListMultisigs(
|
||||
params ListMultisigsParams,
|
||||
) ([]models.Multisig, error) {
|
||||
multisigs, err := i.txRepository.ListMultisig(ctx, transactions.ListMultisigsParams{
|
||||
IDs: params.IDs,
|
||||
OrganizationID: params.OrganizationID,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -44,6 +44,7 @@ type ParticipantParams struct {
|
||||
UsersOnly bool
|
||||
ActiveOnly bool
|
||||
EmployeesOnly bool
|
||||
OwnerOnly bool
|
||||
}
|
||||
|
||||
type ParticipantsParams struct {
|
||||
@ -54,6 +55,7 @@ type ParticipantsParams struct {
|
||||
UsersOnly bool
|
||||
ActiveOnly bool
|
||||
EmployeesOnly bool
|
||||
OwnerOnly bool
|
||||
}
|
||||
|
||||
type OrganizationsInteractor interface {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
||||
"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/repository/transactions"
|
||||
"github.com/google/uuid"
|
||||
@ -59,20 +60,23 @@ type TransactionsInteractor interface {
|
||||
}
|
||||
|
||||
type transactionsInteractor struct {
|
||||
log *slog.Logger
|
||||
txRepo transactions.Repository
|
||||
orgInteractor organizations.OrganizationsInteractor
|
||||
log *slog.Logger
|
||||
txRepo transactions.Repository
|
||||
orgInteractor organizations.OrganizationsInteractor
|
||||
chainInteractor chain.ChainInteractor
|
||||
}
|
||||
|
||||
func NewTransactionsInteractor(
|
||||
log *slog.Logger,
|
||||
txRepo transactions.Repository,
|
||||
orgInteractor organizations.OrganizationsInteractor,
|
||||
chainInteractor chain.ChainInteractor,
|
||||
) TransactionsInteractor {
|
||||
return &transactionsInteractor{
|
||||
log: log,
|
||||
txRepo: txRepo,
|
||||
orgInteractor: orgInteractor,
|
||||
log: log,
|
||||
txRepo: txRepo,
|
||||
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)
|
||||
}
|
||||
|
||||
// TODO confirm tx via chain-api
|
||||
|
||||
tx, err := i.txRepo.GetTransactions(ctx, transactions.GetTransactionsParams{
|
||||
Ids: uuid.UUIDs{params.TxID},
|
||||
OrganizationId: params.OrganizationID,
|
||||
@ -240,6 +246,8 @@ func (i *transactionsInteractor) Confirm(ctx context.Context, params ConfirmPara
|
||||
return tx[0], nil
|
||||
}
|
||||
|
||||
// TODO Execute()
|
||||
|
||||
func (i *transactionsInteractor) Cancel(ctx context.Context, params CancelParams) (*models.Transaction, error) {
|
||||
user, err := ctxmeta.User(ctx)
|
||||
if err != nil {
|
||||
|
@ -54,6 +54,9 @@ type Repository interface {
|
||||
|
||||
AddMultisig(ctx context.Context, multisig 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 {
|
||||
@ -478,6 +481,12 @@ func (r *repositorySQL) ListMultisig(
|
||||
"organization_id": params.OrganizationID,
|
||||
}).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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetch multisigs from database. %w", err)
|
||||
@ -543,6 +552,94 @@ func (r *repositorySQL) ListMultisig(
|
||||
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 {
|
||||
MultisigID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
|
@ -99,7 +99,7 @@ create table if not exists transactions (
|
||||
amount decimal default 0,
|
||||
|
||||
to_addr bytea not null,
|
||||
tx_index bytea default null,
|
||||
tx_index bigint default 0,
|
||||
|
||||
max_fee_allowed decimal default 0,
|
||||
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
|
||||
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 (
|
||||
link_hash varchar(64) primary key,
|
||||
organization_id uuid,
|
||||
@ -201,3 +204,18 @@ create table invites (
|
||||
expired_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