From 866a86208748f7d0713a97113cee29fb495f8db2 Mon Sep 17 00:00:00 2001 From: optclblast Date: Sat, 18 May 2024 15:01:12 +0300 Subject: [PATCH] tmp --- .vscode/launch.json | 9 +- README.md | 1 - backend/.vscode/launch.json | 3 - backend/internal/factory/interactors.go | 14 + backend/internal/factory/interface.go | 14 + backend/internal/factory/repositories.go | 12 +- backend/internal/factory/wire.go | 2 + backend/internal/factory/wire_gen.go | 7 +- .../rest/controllers/organization.go | 9 +- .../interface/rest/controllers/root.go | 3 + .../rest/controllers/transactions.go | 36 ++ backend/internal/interface/rest/server.go | 4 +- backend/internal/pkg/models/tx.go | 2 +- backend/internal/pkg/models/user.go | 11 +- .../interactors/organizations/interactor.go | 85 +++++ .../interactors/transactions/interactor.go | 140 ++++++++ .../repository/organizations/repository.go | 322 ++++++++++++++++-- .../repository/transactions/repository.go | 8 +- backend/migrations/blockd.sql | 8 +- 19 files changed, 645 insertions(+), 45 deletions(-) create mode 100644 backend/internal/interface/rest/controllers/transactions.go create mode 100644 backend/internal/usecase/interactors/transactions/interactor.go diff --git a/.vscode/launch.json b/.vscode/launch.json index c5510a9..5ec3021 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,15 +12,18 @@ "program": "${workspaceRoot}/backend/cmd/main.go", "args": [ "-log-level=debug", - "-log-local=true", + "-log-local=false", "-log-add-source=true", + "-jwt-secret=local_jwt_secret", - "-rest-address=localhost:8080", + "-rest-address=localhost:8081", "-db-host=localhost:8432", "-db-database=blockd", "-db-user=blockd", "-db-secret=blockd", - "-db-enable-tls=false" + "-db-enable-tls=false", + + "-cache-host=localhost:6379" ] } ] diff --git a/README.md b/README.md index e9dddbb..f9a4635 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,3 @@ - On First Login - Owner inputs his SEED_KEY (mnemonic), creates an organization, we save its seed hash for future login and signing internal txs. - When inviting an employee to organization- we generate an invitation link, then after clicking on this link - the user is asked for seed, if he's already registered or able to generate a seed for new account. -- diff --git a/backend/.vscode/launch.json b/backend/.vscode/launch.json index 7fdb1c5..b7ebb00 100644 --- a/backend/.vscode/launch.json +++ b/backend/.vscode/launch.json @@ -1,7 +1,4 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { diff --git a/backend/internal/factory/interactors.go b/backend/internal/factory/interactors.go index f86a786..531bb2b 100644 --- a/backend/internal/factory/interactors.go +++ b/backend/internal/factory/interactors.go @@ -6,10 +6,12 @@ import ( "github.com/emochka2007/block-accounting/internal/pkg/config" "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" "github.com/emochka2007/block-accounting/internal/usecase/repository/auth" "github.com/emochka2007/block-accounting/internal/usecase/repository/cache" orepo "github.com/emochka2007/block-accounting/internal/usecase/repository/organizations" + txRepo "github.com/emochka2007/block-accounting/internal/usecase/repository/transactions" urepo "github.com/emochka2007/block-accounting/internal/usecase/repository/users" ) @@ -35,3 +37,15 @@ func provideOrganizationsInteractor( ) organizations.OrganizationsInteractor { return organizations.NewOrganizationsInteractor(log, orgRepo, cache) } + +func provideTxInteractor( + log *slog.Logger, + txRepo txRepo.Repository, + orgInteractor organizations.OrganizationsInteractor, +) transactions.TransactionsInteractor { + return transactions.NewTransactionsInteractor( + log.WithGroup("transaction-interactor"), + txRepo, + orgInteractor, + ) +} diff --git a/backend/internal/factory/interface.go b/backend/internal/factory/interface.go index 723a33e..bf17694 100644 --- a/backend/internal/factory/interface.go +++ b/backend/internal/factory/interface.go @@ -13,6 +13,7 @@ import ( "github.com/emochka2007/block-accounting/internal/pkg/logger" "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" ) @@ -20,6 +21,7 @@ var interfaceSet wire.ProviderSet = wire.NewSet( provideAuthController, provideOrganizationsController, provideControllers, + provideTxController, provideAuthPresenter, provideOrganizationsPresenter, @@ -84,15 +86,27 @@ func provideOrganizationsController( ) } +func provideTxController( + log *slog.Logger, + txInteractor transactions.TransactionsInteractor, +) controllers.TransactionsController { + return controllers.NewTransactionsController( + log.WithGroup("transactions-controller"), + txInteractor, + ) +} + func provideControllers( log *slog.Logger, authController controllers.AuthController, orgController controllers.OrganizationsController, + txController controllers.TransactionsController, ) *controllers.RootController { return &controllers.RootController{ Ping: controllers.NewPingController(log.WithGroup("ping-controller")), Auth: authController, Organizations: orgController, + Transactions: txController, } } diff --git a/backend/internal/factory/repositories.go b/backend/internal/factory/repositories.go index 1c10c45..be61dde 100644 --- a/backend/internal/factory/repositories.go +++ b/backend/internal/factory/repositories.go @@ -8,6 +8,7 @@ import ( "github.com/emochka2007/block-accounting/internal/usecase/repository/auth" "github.com/emochka2007/block-accounting/internal/usecase/repository/cache" "github.com/emochka2007/block-accounting/internal/usecase/repository/organizations" + "github.com/emochka2007/block-accounting/internal/usecase/repository/transactions" "github.com/emochka2007/block-accounting/internal/usecase/repository/users" "github.com/redis/go-redis/v9" ) @@ -16,8 +17,15 @@ func provideUsersRepository(db *sql.DB) users.Repository { return users.NewRepository(db) } -func provideOrganizationsRepository(db *sql.DB) organizations.Repository { - return organizations.NewRepository(db) +func provideOrganizationsRepository( + db *sql.DB, + uRepo users.Repository, +) organizations.Repository { + return organizations.NewRepository(db, uRepo) +} + +func provideTxRepository(db *sql.DB) transactions.Repository { + return transactions.NewRepository(db) } func provideAuthRepository(db *sql.DB) auth.Repository { diff --git a/backend/internal/factory/wire.go b/backend/internal/factory/wire.go index 3558957..ca44f89 100644 --- a/backend/internal/factory/wire.go +++ b/backend/internal/factory/wire.go @@ -18,8 +18,10 @@ func ProvideService(c config.Config) (service.Service, func(), error) { provideRedisCache, provideUsersRepository, provideUsersInteractor, + provideTxRepository, provideOrganizationsRepository, provideOrganizationsInteractor, + provideTxInteractor, provideAuthRepository, provideJWTInteractor, interfaceSet, diff --git a/backend/internal/factory/wire_gen.go b/backend/internal/factory/wire_gen.go index 8402230..78f2e43 100644 --- a/backend/internal/factory/wire_gen.go +++ b/backend/internal/factory/wire_gen.go @@ -26,13 +26,16 @@ func ProvideService(c config.Config) (service.Service, func(), error) { jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository) authPresenter := provideAuthPresenter(jwtInteractor) authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor) - organizationsRepository := provideOrganizationsRepository(db) + organizationsRepository := provideOrganizationsRepository(db, usersRepository) client, cleanup2 := provideRedisConnection(c) cache := provideRedisCache(client, logger) organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache) organizationsPresenter := provideOrganizationsPresenter() organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter) - rootController := provideControllers(logger, authController, organizationsController) + transactionsRepository := provideTxRepository(db) + transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor) + transactionsController := provideTxController(logger, transactionsInteractor) + rootController := provideControllers(logger, authController, organizationsController, transactionsController) server := provideRestServer(logger, rootController, c, jwtInteractor) serviceService := service.NewService(logger, server) return serviceService, func() { diff --git a/backend/internal/interface/rest/controllers/organization.go b/backend/internal/interface/rest/controllers/organization.go index 44c0a51..95a3014 100644 --- a/backend/internal/interface/rest/controllers/organization.go +++ b/backend/internal/interface/rest/controllers/organization.go @@ -9,13 +9,14 @@ import ( "github.com/emochka2007/block-accounting/internal/interface/rest/domain" "github.com/emochka2007/block-accounting/internal/interface/rest/presenters" - "github.com/emochka2007/block-accounting/internal/pkg/ctxmeta" "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations" ) type OrganizationsController interface { NewOrganization(w http.ResponseWriter, r *http.Request) ([]byte, error) ListOrganizations(w http.ResponseWriter, r *http.Request) ([]byte, error) + // todo delete + // todo update } type organizationsController struct { @@ -66,13 +67,7 @@ func (c *organizationsController) ListOrganizations(w http.ResponseWriter, r *ht ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() - user, err := ctxmeta.User(ctx) - if err != nil { - return nil, fmt.Errorf("error fetch user from context. %w", err) - } - resp, err := c.orgInteractor.List(ctx, organizations.ListParams{ - UserId: user.Id(), Cursor: req.Cursor, Limit: req.Limit, OffsetDate: time.UnixMilli(req.OffsetDate), diff --git a/backend/internal/interface/rest/controllers/root.go b/backend/internal/interface/rest/controllers/root.go index 4fcfb35..46acaa2 100644 --- a/backend/internal/interface/rest/controllers/root.go +++ b/backend/internal/interface/rest/controllers/root.go @@ -4,16 +4,19 @@ type RootController struct { Ping PingController Auth AuthController Organizations OrganizationsController + Transactions TransactionsController } func NewRootController( ping PingController, auth AuthController, organizations OrganizationsController, + transactions TransactionsController, ) *RootController { return &RootController{ Ping: ping, Auth: auth, Organizations: organizations, + Transactions: transactions, } } diff --git a/backend/internal/interface/rest/controllers/transactions.go b/backend/internal/interface/rest/controllers/transactions.go new file mode 100644 index 0000000..74ba2cd --- /dev/null +++ b/backend/internal/interface/rest/controllers/transactions.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "log/slog" + "net/http" + + "github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions" +) + +type TransactionsController interface { + New(w http.ResponseWriter, r *http.Request) ([]byte, error) + List(w http.ResponseWriter, r *http.Request) ([]byte, error) +} + +type transactionsController struct { + log *slog.Logger + txInteractor transactions.TransactionsInteractor +} + +func NewTransactionsController( + log *slog.Logger, + txInteractor transactions.TransactionsInteractor, +) TransactionsController { + return &transactionsController{ + log: log, + txInteractor: txInteractor, + } +} + +func (c *transactionsController) New(w http.ResponseWriter, r *http.Request) ([]byte, error) { + panic("implement me!") +} + +func (c *transactionsController) List(w http.ResponseWriter, r *http.Request) ([]byte, error) { + panic("implement me!") +} diff --git a/backend/internal/interface/rest/server.go b/backend/internal/interface/rest/server.go index 386ee0a..c71f723 100644 --- a/backend/internal/interface/rest/server.go +++ b/backend/internal/interface/rest/server.go @@ -109,8 +109,8 @@ func (s *Server) buildRouter() { // r.Delete("/", s.handle(s.controllers.Organizations.NewOrganization, "delete_organization")) r.Route("/transactions", func(r chi.Router) { - r.Get("/", nil) // list todo add cache - r.Post("/", nil) // add + r.Get("/", s.handle(s.controllers.Transactions.List, "tx_list")) + r.Post("/", s.handle(s.controllers.Transactions.New, "new_tx")) r.Put("/{tx_id}", nil) // update / approve (or maybe body?) r.Delete("/{tx_id}", nil) // remove }) diff --git a/backend/internal/pkg/models/tx.go b/backend/internal/pkg/models/tx.go index 52054be..7b9cbab 100644 --- a/backend/internal/pkg/models/tx.go +++ b/backend/internal/pkg/models/tx.go @@ -11,7 +11,7 @@ type Transaction struct { Description string OrganizationId uuid.UUID - CreatedBy *User + CreatedBy *OrganizationUser Amount int64 ToAddr []byte diff --git a/backend/internal/pkg/models/user.go b/backend/internal/pkg/models/user.go index 61e28a6..bbf149a 100644 --- a/backend/internal/pkg/models/user.go +++ b/backend/internal/pkg/models/user.go @@ -60,7 +60,7 @@ const ( ) type OrganizationParticipant interface { - UserIdentity + Id() uuid.UUID Type() OrganizationParticipantType @@ -102,12 +102,21 @@ func (u *OrganizationUser) Position() string { type Employee struct { ID uuid.UUID + UserID uuid.UUID OrganizationId uuid.UUID WalletAddress []byte CreatedAt time.Time UpdatedAt time.Time } +func (u *Employee) Id() uuid.UUID { + return u.ID +} + +func (u *Employee) UserId() uuid.UUID { + return u.UserID +} + func (u *Employee) Type() OrganizationParticipantType { return OrganizationParticipantTypeEmployee } diff --git a/backend/internal/usecase/interactors/organizations/interactor.go b/backend/internal/usecase/interactors/organizations/interactor.go index 59019ff..14776d4 100644 --- a/backend/internal/usecase/interactors/organizations/interactor.go +++ b/backend/internal/usecase/interactors/organizations/interactor.go @@ -37,6 +37,24 @@ type ListParams struct { Limit uint8 // Max limit is 50 (may change) } +type ParticipantParams struct { + ID uuid.UUID + OrganizationID uuid.UUID + + UsersOnly bool + ActiveOnly bool + EmployeesOnly bool +} + +type ParticipantsParams struct { + IDs uuid.UUIDs + OrganizationID uuid.UUID + + UsersOnly bool + ActiveOnly bool + EmployeesOnly bool +} + type OrganizationsInteractor interface { Create( ctx context.Context, @@ -46,6 +64,14 @@ type OrganizationsInteractor interface { 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) } type organizationsInteractor struct { @@ -226,3 +252,62 @@ func (i *organizationsInteractor) Create( return &org, nil } + +func (i *organizationsInteractor) Participant( + ctx context.Context, + params ParticipantParams, +) (models.OrganizationParticipant, error) { + participants, err := i.Participants(ctx, ParticipantsParams{ + IDs: uuid.UUIDs{params.ID}, + OrganizationID: params.OrganizationID, + ActiveOnly: params.ActiveOnly, + UsersOnly: params.UsersOnly, + EmployeesOnly: params.EmployeesOnly, + }) + if err != nil { + return nil, fmt.Errorf("error fetch organization participant. %w", err) + } + + if len(participants) == 0 { + return nil, fmt.Errorf("error organization participant empty. %w", err) + } + + return participants[0], nil +} + +func (i *organizationsInteractor) Participants( + ctx context.Context, + params ParticipantsParams, +) ([]models.OrganizationParticipant, error) { + // TODO check access for ctx user + user, err := ctxmeta.User(ctx) + if err != nil { + return nil, fmt.Errorf("error fetch user from context. %w", err) + } + + _, err = i.orgRepository.Participants(ctx, organizations.ParticipantsParams{ + Ids: uuid.UUIDs{user.Id()}, + OrganizationId: params.OrganizationID, + ActiveOnly: params.ActiveOnly, + UsersOnly: true, + }) + if err != nil { + return nil, errors.Join( + fmt.Errorf("error fetch organization user. %w", err), + ErrorUnauthorizedAccess, + ) + } + + participants, err := i.orgRepository.Participants(ctx, organizations.ParticipantsParams{ + Ids: params.IDs, + OrganizationId: params.OrganizationID, + UsersOnly: params.UsersOnly, + EmployeesOnly: params.EmployeesOnly, + ActiveOnly: params.ActiveOnly, + }) + if err != nil { + return nil, fmt.Errorf("error fetch organization participants. %w", err) + } + + return participants, nil +} diff --git a/backend/internal/usecase/interactors/transactions/interactor.go b/backend/internal/usecase/interactors/transactions/interactor.go new file mode 100644 index 0000000..009e0d1 --- /dev/null +++ b/backend/internal/usecase/interactors/transactions/interactor.go @@ -0,0 +1,140 @@ +package transactions + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/emochka2007/block-accounting/internal/pkg/ctxmeta" + "github.com/emochka2007/block-accounting/internal/pkg/models" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations" + "github.com/emochka2007/block-accounting/internal/usecase/repository/transactions" + "github.com/google/uuid" +) + +type ListParams struct { + IDs uuid.UUIDs + OrganizationID uuid.UUID + CreatedBy uuid.UUID + + To []byte + + Limit int64 + Cursor string + + WithCancelled bool + WithConfirmed bool + WithCommited bool + WithExpired bool + + WithConfirmations bool +} + +type CreateParams struct { + Tx models.Transaction + OrganizationId uuid.UUID +} + +type ConfirmParams struct { + TxID uuid.UUID + OrganizationID uuid.UUID +} + +type CancelParams struct { + TxID uuid.UUID + OrganizationID uuid.UUID + Cause string +} + +type TransactionsInteractor interface { + List(ctx context.Context, params ListParams) ([]*models.Transaction, error) + Create(ctx context.Context, params CreateParams) (*models.Transaction, error) + Confirm(ctx context.Context, params ConfirmParams) (*models.Transaction, error) + Cancel(ctx context.Context, params CancelParams) (*models.Transaction, error) + // TODO delete + // TODO update +} + +type transactionsInteractor struct { + log *slog.Logger + txRepo transactions.Repository + orgInteractor organizations.OrganizationsInteractor +} + +func NewTransactionsInteractor( + log *slog.Logger, + txRepo transactions.Repository, + orgInteractor organizations.OrganizationsInteractor, +) TransactionsInteractor { + return &transactionsInteractor{ + log: log, + txRepo: txRepo, + orgInteractor: orgInteractor, + } +} + +func (i *transactionsInteractor) List(ctx context.Context, params ListParams) ([]*models.Transaction, error) { + filters := make([]transactions.GetTransactionsFilter, 0) + + if params.WithCancelled { + filters = append(filters, transactions.GetFilterCancelled) + } + + if params.WithConfirmed { + filters = append(filters, transactions.GetFilterConfirmed) + } + + if params.WithCommited { + filters = append(filters, transactions.GetFilterCommited) + } + + txs, err := i.txRepo.GetTransactions(ctx, transactions.GetTransactionsParams{ + Ids: params.IDs, + OrganizationId: params.OrganizationID, + CreatedById: params.CreatedBy, + Filters: filters, + }) + if err != nil { + return nil, fmt.Errorf("error fetch transaction from repository. %w", err) + } + + return txs, nil +} + +func (i *transactionsInteractor) Create( + ctx context.Context, + params CreateParams, +) (*models.Transaction, error) { + user, err := ctxmeta.User(ctx) + if err != nil { + return nil, fmt.Errorf("error fetch user from context. %w", err) + } + + tx := params.Tx + + participant, err := i.orgInteractor.Participant(ctx, organizations.ParticipantParams{ + ID: user.Id(), + ActiveOnly: true, + UsersOnly: true, + }) + if err != nil { + return nil, fmt.Errorf("error fetch actor prticipant. %w", err) + } + + tx.CreatedBy = participant.GetUser() + tx.CreatedAt = time.Now() + + if err = i.txRepo.CreateTransaction(ctx, tx); err != nil { + return nil, fmt.Errorf("error create new tx. %w", err) + } + + return &tx, nil +} + +func (i *transactionsInteractor) Confirm(ctx context.Context, params ConfirmParams) (*models.Transaction, error) { + panic("implement me!") +} +func (i *transactionsInteractor) Cancel(ctx context.Context, params CancelParams) (*models.Transaction, error) { + panic("implement me!") +} diff --git a/backend/internal/usecase/repository/organizations/repository.go b/backend/internal/usecase/repository/organizations/repository.go index 3a0cbb5..7dee84c 100644 --- a/backend/internal/usecase/repository/organizations/repository.go +++ b/backend/internal/usecase/repository/organizations/repository.go @@ -10,7 +10,13 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/emochka2007/block-accounting/internal/pkg/models" sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils" + "github.com/emochka2007/block-accounting/internal/usecase/repository/users" "github.com/google/uuid" + "golang.org/x/sync/errgroup" +) + +var ( + ErrorNotFound = errors.New("not found") ) type GetParams struct { @@ -51,17 +57,23 @@ type Repository interface { Update(ctx context.Context, org models.Organization) error Delete(ctx context.Context, id uuid.UUID) error AddParticipant(ctx context.Context, params AddParticipantParams) error + 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 } type repositorySQL struct { - db *sql.DB + db *sql.DB + usersRepository users.Repository } -func NewRepository(db *sql.DB) Repository { +func NewRepository( + db *sql.DB, + usersRepository users.Repository, +) Repository { return &repositorySQL{ - db: db, + db: db, + usersRepository: usersRepository, } } @@ -262,22 +274,6 @@ func (r *repositorySQL) CreateAndAdd(ctx context.Context, org models.Organizatio return nil } -func (r *repositorySQL) Participants( - ctx context.Context, - params ParticipantsParams, -) ([]models.OrganizationParticipant, error) { - participants := make([]models.OrganizationParticipant, 0, len(params.Ids)) - - if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { - - return nil - }); err != nil { - - } - - return participants, nil -} - func (r *repositorySQL) AddParticipant(ctx context.Context, params AddParticipantParams) error { if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { query := sq.Insert("organizations_users"). @@ -349,3 +345,291 @@ func (r *repositorySQL) DeleteParticipant(ctx context.Context, params DeletePart return nil } + +func (r *repositorySQL) Participants( + ctx context.Context, + params ParticipantsParams, +) ([]models.OrganizationParticipant, error) { + participants := make([]models.OrganizationParticipant, 0, len(params.Ids)) + + if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { + orgUsersModels, err := r.fetchOrganizationUsers(ctx, params) + if err != nil { + return fmt.Errorf("error fetch organization users raw models. %w", err) + } + + eg, egCtx := errgroup.WithContext(ctx) + + var employees []*models.Employee = make([]*models.Employee, 0, len(orgUsersModels)) + eg.Go(func() error { + ids := make(uuid.UUIDs, 0, len(orgUsersModels)) + + for _, m := range orgUsersModels { + if m.employeeID != uuid.Nil { + ids = append(ids, m.employeeID) + } + } + + employees, err = r.fetchEmployees(egCtx, fetchEmployeesParams{ + IDs: ids, + OrganizationId: params.OrganizationId, + }) + if err != nil { + return fmt.Errorf("error fetch employees. %w", err) + } + + return nil + }) + + var usrs []*models.User + eg.Go(func() error { + ids := make(uuid.UUIDs, 0, len(orgUsersModels)) + + for _, m := range orgUsersModels { + if m.userID != uuid.Nil { + ids = append(ids, m.employeeID) + } + } + + usrs, err = r.usersRepository.Get(egCtx, users.GetParams{ + Ids: ids, + }) + if err != nil { + return fmt.Errorf("error fetch users by ids. %w", err) + } + + return nil + }) + + if err = eg.Wait(); err != nil { + return fmt.Errorf("error organizations users entitiels. %w", err) + } + + for _, ou := range orgUsersModels { + var employee *models.Employee + + if ou.employeeID != uuid.Nil { + for _, e := range employees { + if e.ID == ou.employeeID { + employee = e + + break + } + } + } + + if ou.userID == uuid.Nil && employee != nil { + participants = append(participants, employee) + } + + var orgUser *models.OrganizationUser + + for _, u := range usrs { + if u.Id() == ou.userID { + orgUser = &models.OrganizationUser{ + User: *u, + OrgPosition: ou.position, + Admin: ou.isAdmin, + Employee: employee, + } + } + } + + participants = append(participants, orgUser) + } + + return nil + }); err != nil { + return nil, fmt.Errorf("error execute transactional operation. %w", err) + } + + if len(participants) == 0 { + return nil, ErrorNotFound + } + + return participants, nil +} + +type fetchOrganizationUsersModel struct { + organizationID uuid.UUID + userID uuid.UUID + employeeID uuid.UUID + position string + addedAt time.Time + updatedAt time.Time + deletedAt time.Time + isAdmin bool +} + +func (r *repositorySQL) fetchOrganizationUsers( + ctx context.Context, + params ParticipantsParams, +) ([]fetchOrganizationUsersModel, error) { + participants := make([]fetchOrganizationUsersModel, 0, len(params.Ids)) + + if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { + ouQuery := sq.Select( + "ou.organization_id", + "ou.user_id", + "ou.employee_id", + "ou.position", + "ou.added_at", + "ou.updated_at", + "ou.deleted_at", + "ou.is_admin", + ).Where(sq.Eq{ + "ou.organization_id": params.OrganizationId, + }).PlaceholderFormat(sq.Dollar) + + if len(params.Ids) > 0 { + ouQuery = ouQuery.Where(sq.Eq{ + "ou.user_id": params.Ids, + }) + } + + rows, err := ouQuery.RunWith(r.Conn(ctx)).QueryContext(ctx) + if err != nil { + return fmt.Errorf("error fetch organization participants. %w", err) + } + + defer func() { + if closeErr := rows.Close(); closeErr != nil { + err = errors.Join(fmt.Errorf("error close rows. %w", closeErr), err) + } + }() + + for rows.Next() { + var ( + organizationID uuid.UUID + userID uuid.UUID + employeeID uuid.UUID + position string + addedAt time.Time + updatedAt time.Time + deletedAt sql.NullTime + isAdmin bool + ) + + if err = rows.Scan( + &organizationID, + &userID, + &employeeID, + &position, + &addedAt, + &updatedAt, + &deletedAt, + &isAdmin, + ); err != nil { + return fmt.Errorf("error scan row. %w", err) + } + + if params.EmployeesOnly && employeeID == uuid.Nil { + continue + } + + if params.UsersOnly && userID == uuid.Nil { + continue + } + + if params.ActiveOnly && deletedAt.Valid { + continue + } + + participants = append(participants, fetchOrganizationUsersModel{ + organizationID: organizationID, + userID: userID, + employeeID: employeeID, + position: position, + addedAt: addedAt, + updatedAt: updatedAt, + deletedAt: deletedAt.Time, + isAdmin: isAdmin, + }) + } + + return err + }); err != nil { + return nil, fmt.Errorf("error execute transactional operation. %w", err) + } + + return participants, nil +} + +type fetchEmployeesParams struct { + IDs uuid.UUIDs + OrganizationId uuid.UUID +} + +func (r *repositorySQL) fetchEmployees( + ctx context.Context, + params fetchEmployeesParams, +) ([]*models.Employee, error) { + employees := make([]*models.Employee, 0, len(params.IDs)) + + if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { + query := sq.Select( + "e.id", + "e.user_id", + "e.organization_id", + "e.wallet_address", + "e.created_at", + "e.updated_at", + ).Where(sq.Eq{ + "e.organization_id": params.OrganizationId, + }).PlaceholderFormat(sq.Dollar) + + if len(params.IDs) > 0 { + query = query.Where(sq.Eq{ + "e.id": params.IDs, + }) + } + + rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx) + if err != nil { + return fmt.Errorf("error fetch employees from database. %w", err) + } + + defer func() { + if closeErr := rows.Close(); closeErr != nil { + err = errors.Join(fmt.Errorf("error close rows. %w", closeErr), err) + } + }() + + for rows.Next() { + var ( + id uuid.UUID + userID uuid.UUID + orgID uuid.UUID + walletAddr []byte + createdAt time.Time + updatedAt time.Time + ) + + if err = rows.Scan( + &id, + &userID, + &orgID, + &walletAddr, + &createdAt, + &updatedAt, + ); err != nil { + return fmt.Errorf("error scan row. %w", err) + } + + employees = append(employees, &models.Employee{ + ID: id, + UserID: userID, + OrganizationId: orgID, + WalletAddress: walletAddr, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }) + } + + return nil + }); err != nil { + return nil, fmt.Errorf("error execute transactional operation. %w", err) + } + + return employees, nil +} diff --git a/backend/internal/usecase/repository/transactions/repository.go b/backend/internal/usecase/repository/transactions/repository.go index ceac4eb..37b5a4f 100644 --- a/backend/internal/usecase/repository/transactions/repository.go +++ b/backend/internal/usecase/repository/transactions/repository.go @@ -143,9 +143,11 @@ func (r *repositorySQL) GetTransactions( Amount: amount, ToAddr: toAddr, MaxFeeAllowed: maxFeeAllowed, - CreatedBy: &models.User{ - ID: createdById, - Bip39Seed: createdBySeed, + CreatedBy: &models.OrganizationUser{ + User: models.User{ + ID: createdById, + Bip39Seed: createdBySeed, + }, }, CreatedAt: createdAt, UpdatedAt: updatedAt, diff --git a/backend/migrations/blockd.sql b/backend/migrations/blockd.sql index 5d2be93..43a92e7 100644 --- a/backend/migrations/blockd.sql +++ b/backend/migrations/blockd.sql @@ -54,7 +54,7 @@ create index if not exists index_organizations_id create table employees ( id uuid primary key, - user_id uuid references users(id), + user_id uuid, organization_id uuid not null references organizations(id), wallet_address text not null, created_at timestamp default current_timestamp, @@ -97,6 +97,7 @@ create table if not exists transactions ( amount bigint default 0, to_addr bytea not null, + tx_index bytea default null, max_fee_allowed bigint default 0, deadline timestamp default null, @@ -138,9 +139,14 @@ create table contracts ( address bytea not null, + payload bytea not null, + created_by uuid not null references users(id), organization_id uuid not null references organizations(id), + status tinyint default 0, + tx_index bytea default null, + created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp );