This commit is contained in:
r8zavetr8v 2024-05-18 15:01:12 +03:00
parent 876957aabe
commit 866a862087
19 changed files with 645 additions and 45 deletions

9
.vscode/launch.json vendored
View File

@ -12,15 +12,18 @@
"program": "${workspaceRoot}/backend/cmd/main.go", "program": "${workspaceRoot}/backend/cmd/main.go",
"args": [ "args": [
"-log-level=debug", "-log-level=debug",
"-log-local=true", "-log-local=false",
"-log-add-source=true", "-log-add-source=true",
"-jwt-secret=local_jwt_secret",
"-rest-address=localhost:8080", "-rest-address=localhost:8081",
"-db-host=localhost:8432", "-db-host=localhost:8432",
"-db-database=blockd", "-db-database=blockd",
"-db-user=blockd", "-db-user=blockd",
"-db-secret=blockd", "-db-secret=blockd",
"-db-enable-tls=false" "-db-enable-tls=false",
"-cache-host=localhost:6379"
] ]
} }
] ]

View File

@ -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. - 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. - 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.
-

View File

@ -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", "version": "0.2.0",
"configurations": [ "configurations": [
{ {

View File

@ -6,10 +6,12 @@ import (
"github.com/emochka2007/block-accounting/internal/pkg/config" "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/jwt"
"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/interactors/transactions"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users" "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/auth"
"github.com/emochka2007/block-accounting/internal/usecase/repository/cache" "github.com/emochka2007/block-accounting/internal/usecase/repository/cache"
orepo "github.com/emochka2007/block-accounting/internal/usecase/repository/organizations" 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" urepo "github.com/emochka2007/block-accounting/internal/usecase/repository/users"
) )
@ -35,3 +37,15 @@ func provideOrganizationsInteractor(
) organizations.OrganizationsInteractor { ) organizations.OrganizationsInteractor {
return organizations.NewOrganizationsInteractor(log, orgRepo, cache) 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,
)
}

View File

@ -13,6 +13,7 @@ import (
"github.com/emochka2007/block-accounting/internal/pkg/logger" "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/jwt"
"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/interactors/transactions"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
) )
@ -20,6 +21,7 @@ var interfaceSet wire.ProviderSet = wire.NewSet(
provideAuthController, provideAuthController,
provideOrganizationsController, provideOrganizationsController,
provideControllers, provideControllers,
provideTxController,
provideAuthPresenter, provideAuthPresenter,
provideOrganizationsPresenter, 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( func provideControllers(
log *slog.Logger, log *slog.Logger,
authController controllers.AuthController, authController controllers.AuthController,
orgController controllers.OrganizationsController, orgController controllers.OrganizationsController,
txController controllers.TransactionsController,
) *controllers.RootController { ) *controllers.RootController {
return &controllers.RootController{ return &controllers.RootController{
Ping: controllers.NewPingController(log.WithGroup("ping-controller")), Ping: controllers.NewPingController(log.WithGroup("ping-controller")),
Auth: authController, Auth: authController,
Organizations: orgController, Organizations: orgController,
Transactions: txController,
} }
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/emochka2007/block-accounting/internal/usecase/repository/auth" "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/cache"
"github.com/emochka2007/block-accounting/internal/usecase/repository/organizations" "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/emochka2007/block-accounting/internal/usecase/repository/users"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
@ -16,8 +17,15 @@ func provideUsersRepository(db *sql.DB) users.Repository {
return users.NewRepository(db) return users.NewRepository(db)
} }
func provideOrganizationsRepository(db *sql.DB) organizations.Repository { func provideOrganizationsRepository(
return organizations.NewRepository(db) 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 { func provideAuthRepository(db *sql.DB) auth.Repository {

View File

@ -18,8 +18,10 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
provideRedisCache, provideRedisCache,
provideUsersRepository, provideUsersRepository,
provideUsersInteractor, provideUsersInteractor,
provideTxRepository,
provideOrganizationsRepository, provideOrganizationsRepository,
provideOrganizationsInteractor, provideOrganizationsInteractor,
provideTxInteractor,
provideAuthRepository, provideAuthRepository,
provideJWTInteractor, provideJWTInteractor,
interfaceSet, interfaceSet,

View File

@ -26,13 +26,16 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository) jwtInteractor := provideJWTInteractor(c, usersInteractor, authRepository)
authPresenter := provideAuthPresenter(jwtInteractor) authPresenter := provideAuthPresenter(jwtInteractor)
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor) authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor)
organizationsRepository := provideOrganizationsRepository(db) organizationsRepository := provideOrganizationsRepository(db, usersRepository)
client, cleanup2 := provideRedisConnection(c) client, cleanup2 := provideRedisConnection(c)
cache := provideRedisCache(client, logger) cache := provideRedisCache(client, logger)
organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache) organizationsInteractor := provideOrganizationsInteractor(logger, organizationsRepository, cache)
organizationsPresenter := provideOrganizationsPresenter() organizationsPresenter := provideOrganizationsPresenter()
organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter) 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) server := provideRestServer(logger, rootController, c, jwtInteractor)
serviceService := service.NewService(logger, server) serviceService := service.NewService(logger, server)
return serviceService, func() { return serviceService, func() {

View File

@ -9,13 +9,14 @@ import (
"github.com/emochka2007/block-accounting/internal/interface/rest/domain" "github.com/emochka2007/block-accounting/internal/interface/rest/domain"
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters" "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" "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations"
) )
type OrganizationsController interface { type OrganizationsController interface {
NewOrganization(w http.ResponseWriter, r *http.Request) ([]byte, error) NewOrganization(w http.ResponseWriter, r *http.Request) ([]byte, error)
ListOrganizations(w http.ResponseWriter, r *http.Request) ([]byte, error) ListOrganizations(w http.ResponseWriter, r *http.Request) ([]byte, error)
// todo delete
// todo update
} }
type organizationsController struct { 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) ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel() 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{ resp, err := c.orgInteractor.List(ctx, organizations.ListParams{
UserId: user.Id(),
Cursor: req.Cursor, Cursor: req.Cursor,
Limit: req.Limit, Limit: req.Limit,
OffsetDate: time.UnixMilli(req.OffsetDate), OffsetDate: time.UnixMilli(req.OffsetDate),

View File

@ -4,16 +4,19 @@ type RootController struct {
Ping PingController Ping PingController
Auth AuthController Auth AuthController
Organizations OrganizationsController Organizations OrganizationsController
Transactions TransactionsController
} }
func NewRootController( func NewRootController(
ping PingController, ping PingController,
auth AuthController, auth AuthController,
organizations OrganizationsController, organizations OrganizationsController,
transactions TransactionsController,
) *RootController { ) *RootController {
return &RootController{ return &RootController{
Ping: ping, Ping: ping,
Auth: auth, Auth: auth,
Organizations: organizations, Organizations: organizations,
Transactions: transactions,
} }
} }

View File

@ -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!")
}

View File

@ -109,8 +109,8 @@ func (s *Server) buildRouter() {
// r.Delete("/", s.handle(s.controllers.Organizations.NewOrganization, "delete_organization")) // r.Delete("/", s.handle(s.controllers.Organizations.NewOrganization, "delete_organization"))
r.Route("/transactions", func(r chi.Router) { r.Route("/transactions", func(r chi.Router) {
r.Get("/", nil) // list todo add cache r.Get("/", s.handle(s.controllers.Transactions.List, "tx_list"))
r.Post("/", nil) // add r.Post("/", s.handle(s.controllers.Transactions.New, "new_tx"))
r.Put("/{tx_id}", nil) // update / approve (or maybe body?) r.Put("/{tx_id}", nil) // update / approve (or maybe body?)
r.Delete("/{tx_id}", nil) // remove r.Delete("/{tx_id}", nil) // remove
}) })

View File

@ -11,7 +11,7 @@ type Transaction struct {
Description string Description string
OrganizationId uuid.UUID OrganizationId uuid.UUID
CreatedBy *User CreatedBy *OrganizationUser
Amount int64 Amount int64
ToAddr []byte ToAddr []byte

View File

@ -60,7 +60,7 @@ const (
) )
type OrganizationParticipant interface { type OrganizationParticipant interface {
UserIdentity Id() uuid.UUID
Type() OrganizationParticipantType Type() OrganizationParticipantType
@ -102,12 +102,21 @@ func (u *OrganizationUser) Position() string {
type Employee struct { type Employee struct {
ID uuid.UUID ID uuid.UUID
UserID uuid.UUID
OrganizationId uuid.UUID OrganizationId uuid.UUID
WalletAddress []byte WalletAddress []byte
CreatedAt time.Time CreatedAt time.Time
UpdatedAt 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 { func (u *Employee) Type() OrganizationParticipantType {
return OrganizationParticipantTypeEmployee return OrganizationParticipantTypeEmployee
} }

View File

@ -37,6 +37,24 @@ type ListParams struct {
Limit uint8 // Max limit is 50 (may change) 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 { type OrganizationsInteractor interface {
Create( Create(
ctx context.Context, ctx context.Context,
@ -46,6 +64,14 @@ type OrganizationsInteractor interface {
ctx context.Context, ctx context.Context,
params ListParams, params ListParams,
) (*ListResponse, error) ) (*ListResponse, error)
Participant(
ctx context.Context,
params ParticipantParams,
) (models.OrganizationParticipant, error)
Participants(
ctx context.Context,
params ParticipantsParams,
) ([]models.OrganizationParticipant, error)
} }
type organizationsInteractor struct { type organizationsInteractor struct {
@ -226,3 +252,62 @@ func (i *organizationsInteractor) Create(
return &org, nil 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
}

View File

@ -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!")
}

View File

@ -10,7 +10,13 @@ import (
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/emochka2007/block-accounting/internal/pkg/models" "github.com/emochka2007/block-accounting/internal/pkg/models"
sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils" sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils"
"github.com/emochka2007/block-accounting/internal/usecase/repository/users"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/sync/errgroup"
)
var (
ErrorNotFound = errors.New("not found")
) )
type GetParams struct { type GetParams struct {
@ -51,17 +57,23 @@ type Repository interface {
Update(ctx context.Context, org models.Organization) error Update(ctx context.Context, org models.Organization) error
Delete(ctx context.Context, id uuid.UUID) error Delete(ctx context.Context, id uuid.UUID) error
AddParticipant(ctx context.Context, params AddParticipantParams) 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 CreateAndAdd(ctx context.Context, org models.Organization, user *models.User) error
DeleteParticipant(ctx context.Context, params DeleteParticipantParams) error DeleteParticipant(ctx context.Context, params DeleteParticipantParams) error
} }
type repositorySQL struct { 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{ return &repositorySQL{
db: db, db: db,
usersRepository: usersRepository,
} }
} }
@ -262,22 +274,6 @@ func (r *repositorySQL) CreateAndAdd(ctx context.Context, org models.Organizatio
return nil 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 { func (r *repositorySQL) AddParticipant(ctx context.Context, params AddParticipantParams) error {
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) {
query := sq.Insert("organizations_users"). query := sq.Insert("organizations_users").
@ -349,3 +345,291 @@ func (r *repositorySQL) DeleteParticipant(ctx context.Context, params DeletePart
return nil 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
}

View File

@ -143,9 +143,11 @@ func (r *repositorySQL) GetTransactions(
Amount: amount, Amount: amount,
ToAddr: toAddr, ToAddr: toAddr,
MaxFeeAllowed: maxFeeAllowed, MaxFeeAllowed: maxFeeAllowed,
CreatedBy: &models.User{ CreatedBy: &models.OrganizationUser{
ID: createdById, User: models.User{
Bip39Seed: createdBySeed, ID: createdById,
Bip39Seed: createdBySeed,
},
}, },
CreatedAt: createdAt, CreatedAt: createdAt,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,

View File

@ -54,7 +54,7 @@ create index if not exists index_organizations_id
create table employees ( create table employees (
id uuid primary key, id uuid primary key,
user_id uuid references users(id), user_id uuid,
organization_id uuid not null references organizations(id), organization_id uuid not null references organizations(id),
wallet_address text not null, wallet_address text not null,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
@ -97,6 +97,7 @@ create table if not exists transactions (
amount bigint default 0, amount bigint default 0,
to_addr bytea not null, to_addr bytea not null,
tx_index bytea default null,
max_fee_allowed bigint default 0, max_fee_allowed bigint default 0,
deadline timestamp default null, deadline timestamp default null,
@ -138,9 +139,14 @@ create table contracts (
address bytea not null, address bytea not null,
payload bytea not null,
created_by uuid not null references users(id), created_by uuid not null references users(id),
organization_id uuid not null references organizations(id), organization_id uuid not null references organizations(id),
status tinyint default 0,
tx_index bytea default null,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp updated_at timestamp default current_timestamp
); );