mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-04-12 08:56:28 +00:00
205 lines
4.6 KiB
Go
205 lines
4.6 KiB
Go
package organizations
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
|
|
"github.com/emochka2007/block-accounting/internal/pkg/hdwallet"
|
|
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
|
"github.com/emochka2007/block-accounting/internal/usecase/repository/organizations"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var (
|
|
ErrorUnauthorizedAccess = errors.New("unauthorized access")
|
|
)
|
|
|
|
type CreateParams struct {
|
|
Name string
|
|
Address string
|
|
WalletMnemonic string
|
|
}
|
|
|
|
type ListParams struct {
|
|
Ids uuid.UUIDs
|
|
UserId uuid.UUID
|
|
|
|
Cursor string
|
|
OffsetDate time.Time
|
|
Limit uint8 // Max limit is 50 (may change)
|
|
}
|
|
|
|
type OrganizationsInteractor interface {
|
|
Create(
|
|
ctx context.Context,
|
|
params CreateParams,
|
|
) (*models.Organization, error)
|
|
List(
|
|
ctx context.Context,
|
|
params ListParams,
|
|
) (*ListResponse, error)
|
|
}
|
|
|
|
type organizationsInteractor struct {
|
|
log *slog.Logger
|
|
orgRepository organizations.Repository
|
|
}
|
|
|
|
func NewOrganizationsInteractor(
|
|
log *slog.Logger,
|
|
orgRepository organizations.Repository,
|
|
) OrganizationsInteractor {
|
|
return &organizationsInteractor{
|
|
log: log,
|
|
orgRepository: orgRepository,
|
|
}
|
|
}
|
|
|
|
type organizationsListCursor struct {
|
|
Id uuid.UUID `json:"id"`
|
|
}
|
|
|
|
func newOrganizationsListCursor(id ...uuid.UUID) *organizationsListCursor {
|
|
if len(id) > 0 {
|
|
return &organizationsListCursor{id[0]}
|
|
}
|
|
|
|
return new(organizationsListCursor)
|
|
}
|
|
|
|
func (c *organizationsListCursor) encode() (string, error) {
|
|
data, err := json.Marshal(c)
|
|
if err != nil {
|
|
return "", fmt.Errorf("couldn't marshal reaction id. %w", err)
|
|
}
|
|
return base64.StdEncoding.EncodeToString(data), nil
|
|
|
|
}
|
|
|
|
func (c *organizationsListCursor) decode(s string) error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
token, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return fmt.Errorf("error decode token. %w", err)
|
|
}
|
|
|
|
return json.Unmarshal(token, c)
|
|
}
|
|
|
|
type ListResponse struct {
|
|
Organizations []*models.Organization
|
|
NextCursor string
|
|
}
|
|
|
|
func (i *organizationsInteractor) List(
|
|
ctx context.Context,
|
|
params ListParams,
|
|
) (*ListResponse, error) {
|
|
user, err := ctxmeta.User(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error fetch user from context. %w", err)
|
|
}
|
|
|
|
if params.UserId != uuid.Nil {
|
|
if params.UserId != user.Id() {
|
|
return nil, fmt.Errorf("error unauthorized organizations list access. %w", ErrorUnauthorizedAccess)
|
|
}
|
|
} else {
|
|
params.UserId = user.Id()
|
|
}
|
|
|
|
if params.Limit <= 0 || params.Limit > 50 {
|
|
params.Limit = 50
|
|
}
|
|
|
|
cursor := newOrganizationsListCursor()
|
|
|
|
if params.Cursor != "" {
|
|
if err := cursor.decode(params.Cursor); err != nil {
|
|
return nil, fmt.Errorf("error decode cursor value. %w", err) // maybe just log error?
|
|
}
|
|
}
|
|
|
|
i.log.Debug(
|
|
"organizations_list",
|
|
slog.String("cursor", params.Cursor),
|
|
slog.Int("limit", int(params.Limit)),
|
|
slog.Any("cursor-id", cursor.Id),
|
|
slog.Any("ids", params.Ids),
|
|
slog.Any("user_id", params.UserId),
|
|
)
|
|
|
|
orgs, err := i.orgRepository.Get(ctx, organizations.GetParams{
|
|
UserId: params.UserId,
|
|
Ids: params.Ids,
|
|
OffsetDate: params.OffsetDate,
|
|
Limit: int64(params.Limit),
|
|
CursorId: cursor.Id,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error fetch organizations. %w", err)
|
|
}
|
|
|
|
var nextCursor string
|
|
|
|
// BUG: pagination by cursor works with errors. empty set as return value
|
|
if len(orgs) >= 50 || len(orgs) >= int(params.Limit) {
|
|
cursor.Id = orgs[len(orgs)-1].ID
|
|
if nextCursor, err = cursor.encode(); err != nil {
|
|
return nil, fmt.Errorf("error encode next page token. %w", err) // maybe just log error?
|
|
}
|
|
}
|
|
|
|
return &ListResponse{
|
|
Organizations: orgs,
|
|
NextCursor: nextCursor,
|
|
}, nil
|
|
}
|
|
|
|
func (i *organizationsInteractor) Create(
|
|
ctx context.Context,
|
|
params CreateParams,
|
|
) (*models.Organization, error) {
|
|
var walletSeed []byte
|
|
|
|
user, err := ctxmeta.User(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error fetch user from context. %w", err)
|
|
}
|
|
|
|
if params.WalletMnemonic == "" {
|
|
walletSeed = user.Seed()
|
|
} else {
|
|
seed, err := hdwallet.NewSeedFromMnemonic(params.WalletMnemonic)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error convert organization wallet mnemonic into a seed. %w", err)
|
|
}
|
|
|
|
walletSeed = seed
|
|
}
|
|
|
|
org := models.Organization{
|
|
ID: uuid.Must(uuid.NewV7()),
|
|
Name: params.Name,
|
|
Address: params.Address,
|
|
WalletSeed: walletSeed,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
if err := i.orgRepository.CreateAndAdd(ctx, org, user); err != nil {
|
|
return nil, fmt.Errorf("error create new organization. %w", err)
|
|
}
|
|
|
|
return &org, nil
|
|
}
|