This commit is contained in:
r8zavetr8v 2024-05-25 01:45:56 +03:00
parent 4c17f84b7e
commit 43dbe185ea
10 changed files with 299 additions and 43 deletions

View File

@ -17,6 +17,7 @@ import (
type ParticipantsController interface {
List(w http.ResponseWriter, r *http.Request) ([]byte, error)
New(w http.ResponseWriter, r *http.Request) ([]byte, error)
}
type participantsController struct {
@ -92,3 +93,30 @@ func (c *participantsController) List(w http.ResponseWriter, r *http.Request) ([
return c.presenter.ResponseListParticipants(ctx, participants)
}
func (c *participantsController) New(w http.ResponseWriter, r *http.Request) ([]byte, error) {
req, err := presenters.CreateRequest[domain.AddEmployeeRequest](r)
if err != nil {
return nil, fmt.Errorf("error build list participants request. %w", err)
}
organizationID, err := ctxmeta.OrganizationId(r.Context())
if err != nil {
return nil, fmt.Errorf("error fetch organization id from context. %w", err)
}
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
participant, err := c.orgInteractor.AddParticipant(ctx, organizations.AddParticipantParams{
OrganizationID: organizationID,
Name: req.Name,
Position: req.Position,
WalletAddress: req.WalletAddress,
})
if err != nil {
return nil, fmt.Errorf("error create new participant. %w", err)
}
return c.presenter.ResponseParticipant(ctx, participant)
}

View File

@ -16,6 +16,10 @@ type ParticipantsPresenter interface {
ctx context.Context,
participants []models.OrganizationParticipant,
) ([]byte, error)
ResponseParticipant(
ctx context.Context,
participant models.OrganizationParticipant,
) ([]byte, error)
}
type participantsPresenter struct{}
@ -116,3 +120,20 @@ func (p *participantsPresenter) ResponseListParticipants(
return out, nil
}
func (p *participantsPresenter) ResponseParticipant(
ctx context.Context,
participant models.OrganizationParticipant,
) ([]byte, error) {
r, err := p.responseParticipant(ctx, participant)
if err != nil {
return nil, fmt.Errorf("error map participant to hal resource. %w", err)
}
out, err := json.Marshal(r)
if err != nil {
return nil, fmt.Errorf("error marshal organization create response. %w", err)
}
return out, nil
}

View File

@ -103,9 +103,7 @@ func (s *Server) buildRouter() {
r.Post("/", s.handle(s.controllers.Organizations.NewOrganization, "new_organization"))
r.Route("/{organization_id}", func(r chi.Router) {
// r.Put("/", s.handle(s.controllers.Organizations.NewOrganization, "update_organization"))
// r.Delete("/", s.handle(s.controllers.Organizations.NewOrganization, "delete_organization"))
// 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"))
@ -115,8 +113,19 @@ func (s *Server) buildRouter() {
)
})
r.Route("/payout", func(r chi.Router) {
r.Post("/", nil)
r.Route("/payrolls", func(r chi.Router) {
r.Get("/", nil) // list payrolls
r.Post("/", nil) // deploy contract
})
r.Route("/multisig", func(r chi.Router) {
r.Post("/", nil) // new multisig (deploy)
r.Get("/", nil) // list
})
r.Route("/license", func(r chi.Router) {
r.Get("/", nil) // list license
r.Post("/", nil) // deploy contract
})
// join via invite link
@ -124,19 +133,21 @@ func (s *Server) buildRouter() {
r.Route("/participants", func(r chi.Router) {
r.Get("/", s.handle(s.controllers.Participants.List, "participants_list"))
r.Post("/", nil)
r.Put("/{participant_id}", nil) // update user / employee
r.Delete("/{participant_id}", nil) // remove user / employee
// generate new invite link
r.Post("/invite", s.handle(s.controllers.Auth.Invite, "invite"))
r.Post("/", nil) // add {employee}
r.Post("/invite", s.handle(s.controllers.Auth.Invite, "invite")) // generate new invite link
})
r.Route("/multisig", func(r chi.Router) {
r.Post("/", nil) // new multisig
r.Get("/", nil) // list
r.Route("/{participant_id}", func(r chi.Router) {
r.Put("/", nil) // update user / employee
r.Delete("/", nil) // remove user / employee
r.Route("/payroll", func(r chi.Router) {
r.Post("/", nil) // set salary
r.Put("/", nil) // edit
r.Get("/", nil)
})
})
})
})
})

View File

@ -1,10 +1,10 @@
package config
type Config struct {
Common CommonConfig
Rest RestConfig
DB DBConfig
Eth EthConfig
Common CommonConfig
Rest RestConfig
DB DBConfig
ChainAPI ChainAPIConfig
}
type CommonConfig struct {
@ -35,6 +35,6 @@ type DBConfig struct {
CacheSecret string
}
type EthConfig struct {
// todo
type ChainAPIConfig struct {
Host string
}

View File

@ -1,24 +1,128 @@
package chain
import (
"bytes"
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"github.com/emochka2007/block-accounting/internal/pkg/config"
"github.com/emochka2007/block-accounting/internal/pkg/models"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
"github.com/emochka2007/block-accounting/internal/usecase/repository/transactions"
"github.com/ethereum/go-ethereum/common"
)
type ChainInteractor interface {
NewMultisig(ctx context.Context, params NewMultisigParams) error
}
type chainInteractor struct {
log *slog.Logger
config config.Config
txRepository transactions.Repository
usersInteractor users.UsersInteractor
}
func NewChainInteractor(
log *slog.Logger,
config config.Config,
txRepository transactions.Repository,
usersInteractor users.UsersInteractor,
) ChainInteractor {
return &chainInteractor{
log: log,
config: config,
txRepository: txRepository,
usersInteractor: usersInteractor,
}
}
type NewMultisigParams struct {
OwnersPKs []string
OwnersPKs []string
Confirmations int
}
func (i *chainInteractor) NewMultisig(ctx context.Context) {
func (i *chainInteractor) NewMultisig(ctx context.Context, params NewMultisigParams) error {
deployAddr := i.config.ChainAPI.Host + "/multi-sig/deploy"
i.log.Debug(
"deploy multisig",
slog.String("endpoint", deployAddr),
slog.Any("params", params),
)
requestBody, err := json.Marshal(map[string]any{
"owners": params.OwnersPKs,
"confirmations": params.Confirmations,
})
if err != nil {
return fmt.Errorf("error marshal request body. %w", err)
}
body := bytes.NewBuffer(requestBody)
doneCh := make(chan struct{})
errCh := make(chan error)
go func() {
resp, err := http.Post(http.MethodPost, deployAddr, body)
if err != nil {
i.log.Error(
"error send deploy multisig request",
slog.String("endpoint", deployAddr),
slog.Any("params", params),
)
errCh <- fmt.Errorf("error build new multisig request. %w", err)
return
}
defer resp.Body.Close()
i.log.Debug(
"deploy multisig response",
slog.Int("code", resp.StatusCode),
)
if _, ok := <-doneCh; ok {
doneCh <- struct{}{}
}
}()
select {
case err := <-errCh:
return err
case <-doneCh:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (i *chainInteractor) PubKey(ctx context.Context, user *models.User) (*ecdsa.PublicKey, error) {
hex := common.Bytes2Hex(user.Seed())
pubAddr := i.config.ChainAPI.Host + "/address/" + hex
doneCh := make(chan struct{})
errCh := make(chan error)
go func() {
resp, err := http.Get(pubAddr)
}()
select {
case err := <-errCh:
return err
case <-doneCh:
return nil
case <-ctx.Done():
return ctx.Err()
}
}

View File

@ -15,6 +15,7 @@ import (
"github.com/emochka2007/block-accounting/internal/pkg/models"
"github.com/emochka2007/block-accounting/internal/usecase/repository/cache"
"github.com/emochka2007/block-accounting/internal/usecase/repository/organizations"
"github.com/ethereum/go-ethereum/common"
"github.com/google/uuid"
)
@ -55,22 +56,12 @@ type ParticipantsParams struct {
}
type OrganizationsInteractor interface {
Create(
ctx context.Context,
params CreateParams,
) (*models.Organization, error)
List(
ctx context.Context,
params ListParams,
) (*ListResponse, error)
Participant(
ctx context.Context,
params ParticipantParams,
) (models.OrganizationParticipant, error)
Participants(
ctx context.Context,
params ParticipantsParams,
) ([]models.OrganizationParticipant, error)
Create(ctx context.Context, params CreateParams) (*models.Organization, error)
List(ctx context.Context, params ListParams) (*ListResponse, error)
Participant(ctx context.Context, params ParticipantParams) (models.OrganizationParticipant, error)
Participants(ctx context.Context, params ParticipantsParams) ([]models.OrganizationParticipant, error)
AddParticipant(ctx context.Context, params AddParticipantParams) (models.OrganizationParticipant, error)
}
type organizationsInteractor struct {
@ -308,3 +299,57 @@ func (i *organizationsInteractor) Participants(
return participants, nil
}
type AddParticipantParams struct {
OrganizationID uuid.UUID
EmployeeUserID uuid.UUID
Name string
Position string
WalletAddress string
}
func (i *organizationsInteractor) AddParticipant(
ctx context.Context,
params AddParticipantParams,
) (models.OrganizationParticipant, error) {
user, err := ctxmeta.User(ctx)
if err != nil {
return nil, fmt.Errorf("error fetch user from context. %w", err)
}
actor, err := i.Participant(ctx, ParticipantParams{
ID: user.Id(),
OrganizationID: params.OrganizationID,
ActiveOnly: true,
UsersOnly: true,
})
if err != nil {
return nil, fmt.Errorf("error fetch actor. %w", err)
}
if !actor.IsOwner() {
return nil, fmt.Errorf("error actor not an owner")
}
if !common.IsHexAddress(params.WalletAddress) {
return nil, fmt.Errorf("error invalid address")
}
participantID := uuid.Must(uuid.NewV7())
empl := models.Employee{
ID: participantID,
EmployeeName: params.Name,
UserID: params.EmployeeUserID,
OrganizationId: params.OrganizationID,
WalletAddress: common.FromHex(params.WalletAddress),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err = i.orgRepository.AddEmployee(ctx, empl); err != nil {
return nil, fmt.Errorf("error add new employee. %w", err)
}
return &empl, nil
}

View File

@ -87,6 +87,10 @@ func (i *usersInteractor) Create(ctx context.Context, params CreateParams) (*mod
Telegram: params.Tg,
}
// TODO fetch user PK from chain-api
user.PK = []byte{0x01}
if err = i.usersRepo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("error create new user. %w", err)
}

View File

@ -61,6 +61,7 @@ type Repository interface {
Participants(ctx context.Context, params ParticipantsParams) ([]models.OrganizationParticipant, error)
CreateAndAdd(ctx context.Context, org models.Organization, user *models.User) error
DeleteParticipant(ctx context.Context, params DeleteParticipantParams) error
AddEmployee(ctx context.Context, employee models.Employee) error
}
type repositorySQL struct {
@ -650,3 +651,41 @@ func (r *repositorySQL) fetchEmployees(
return employees, nil
}
func (r *repositorySQL) AddEmployee(ctx context.Context, employee models.Employee) error {
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
query := sq.Insert("employees").Columns(
"id",
"user_id",
"organization_id",
"wallet_address",
"created_at",
"updated_at",
).Values(
employee.ID,
employee.UserID,
employee.OrganizationId,
employee.WalletAddress,
employee.CreatedAt,
employee.UpdatedAt,
)
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
return fmt.Errorf("error add employee. %w", err)
}
if err := r.AddParticipant(ctx, AddParticipantParams{
OrganizationId: employee.OrganizationId,
UserId: employee.UserID,
EmployeeId: employee.ID,
}); err != nil {
return fmt.Errorf("error add employee to organization. %w", err)
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -50,7 +50,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
var users []*models.User = make([]*models.User, 0, len(params.Ids))
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) {
query := sq.Select("u.id, u.name, u.email, u.phone, u.tg, u.seed, u.created_at, u.activated_at").
query := sq.Select("u.id, u.name, u.email, u.phone, u.tg, u.seed, u.created_at, u.activated_at, u.public_key").
From("users as u").
PlaceholderFormat(sq.Dollar)
@ -95,6 +95,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
tg string
seed []byte
pk []byte
//isAdmin bool
createdAt time.Time
activatedAt sql.NullTime
@ -109,6 +110,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
&seed,
&createdAt,
&activatedAt,
&pk,
); err != nil {
return fmt.Errorf("error scan row. %w", err)
}
@ -122,6 +124,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
Telegram: tg,
},
Bip39Seed: seed,
PK: pk,
//Admin: isAdmin,
CreatedAt: createdAt,
Activated: activatedAt.Valid,
@ -138,7 +141,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
func (r *repositorySQL) Create(ctx context.Context, user *models.User) error {
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
columns := []string{"id", "name", "email", "phone", "tg", "seed", "created_at"}
columns := []string{"id", "name", "email", "phone", "tg", "seed", "public_key", "created_at"}
values := []any{
user.ID,
@ -147,6 +150,7 @@ func (r *repositorySQL) Create(ctx context.Context, user *models.User) error {
user.Credentails.Phone,
user.Credentails.Telegram,
user.Bip39Seed,
user.PK,
user.CreatedAt,
}

View File

@ -158,7 +158,7 @@ create table multisigs (
create table multisig_owners (
multisig_id uuid references multisigs(id),
owner_id uuid references organizations_users(user_id),
owner_id uuid references users(id),
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
primary key (multisig_id, owner_id)
@ -166,7 +166,7 @@ create table multisig_owners (
create table multisig_confirmations (
multisig_id uuid references multisigs(id),
owner_id uuid references organizations_users(user_id),
owner_id uuid references users(id),
created_at timestamp default current_timestamp,
primary key (multisig_id, owner_id)
);