mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-04-12 08:56:28 +00:00
tmp tx storage && user repo
This commit is contained in:
parent
658dc000b8
commit
3ac070d12a
@ -3,10 +3,12 @@ module github.com/emochka2007/block-accounting
|
|||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/go-chi/chi/v5 v5.0.12
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/google/wire v0.6.0
|
github.com/google/wire v0.6.0
|
||||||
github.com/urfave/cli/v2 v2.27.2
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
golang.org/x/crypto v0.18.0
|
golang.org/x/crypto v0.18.0
|
||||||
@ -15,6 +17,8 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||||
|
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
@ -12,16 +16,25 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -9,6 +10,10 @@ import (
|
|||||||
"github.com/emochka2007/block-accounting/internal/pkg/bip32"
|
"github.com/emochka2007/block-accounting/internal/pkg/bip32"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorAuthInvalidMnemonic = errors.New("Invalid Mnemonic")
|
||||||
|
)
|
||||||
|
|
||||||
type AuthController interface {
|
type AuthController interface {
|
||||||
Join(w http.ResponseWriter, req *http.Request) error
|
Join(w http.ResponseWriter, req *http.Request) error
|
||||||
Login(w http.ResponseWriter, req *http.Request) error
|
Login(w http.ResponseWriter, req *http.Request) error
|
||||||
@ -34,19 +39,20 @@ func NewAuthController(
|
|||||||
const mnemonicEntropyBitSize int = 256
|
const mnemonicEntropyBitSize int = 256
|
||||||
|
|
||||||
func (c *authController) Join(w http.ResponseWriter, req *http.Request) error {
|
func (c *authController) Join(w http.ResponseWriter, req *http.Request) error {
|
||||||
entropy, err := bip32.NewEntropy(mnemonicEntropyBitSize)
|
request, err := c.presenter.CreateJoinRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generate new entropy. %w", err)
|
return fmt.Errorf("error create join request. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mnemonic, err := bip32.NewMnemonic(entropy)
|
c.log.Debug("join request", slog.String("mnemonic", request.Mnemonic))
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error generate mnemonic from entropy. %w", err)
|
if !bip32.IsMnemonicValid(request.Mnemonic) {
|
||||||
|
return fmt.Errorf("error invalid mnemonic. %w", ErrorAuthInvalidMnemonic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo create user
|
// todo create user
|
||||||
|
|
||||||
return c.presenter.ResponseJoin(w, mnemonic)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *authController) Login(w http.ResponseWriter, req *http.Request) error {
|
func (c *authController) Login(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JoinResponse struct {
|
type JoinRequest struct {
|
||||||
Mnemonic string `json:"mnemonic"`
|
Mnemonic string `json:"mnemonic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,13 +3,15 @@ package presenters
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
|
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthPresenter interface {
|
type AuthPresenter interface {
|
||||||
ResponseJoin(w http.ResponseWriter, mnemonic string) error
|
CreateJoinRequest(r *http.Request) (*domain.JoinRequest, error)
|
||||||
|
// ResponseJoin(w http.ResponseWriter, mnemonic string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type authPresenter struct{}
|
type authPresenter struct{}
|
||||||
@ -18,17 +20,34 @@ func NewAuthPresenter() AuthPresenter {
|
|||||||
return &authPresenter{}
|
return &authPresenter{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *authPresenter) ResponseJoin(w http.ResponseWriter, mnemonic string) error {
|
func (p *authPresenter) CreateJoinRequest(r *http.Request) (*domain.JoinRequest, error) {
|
||||||
out, err := json.Marshal(domain.JoinResponse{
|
defer r.Body.Close()
|
||||||
Mnemonic: mnemonic,
|
|
||||||
})
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error marshal join response. %w", err)
|
return nil, fmt.Errorf("error read request body. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = w.Write(out); err != nil {
|
var request domain.JoinRequest
|
||||||
return fmt.Errorf("error write response. %w", err)
|
|
||||||
|
if err := json.Unmarshal(data, &request); err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshal join request. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return &request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (p *authPresenter) ResponseJoin(w http.ResponseWriter, mnemonic string) error {
|
||||||
|
// out, err := json.Marshal(domain.JoinResponse{
|
||||||
|
// Mnemonic: mnemonic,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("error marshal join response. %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if _, err = w.Write(out); err != nil {
|
||||||
|
// return fmt.Errorf("error write response. %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
@ -92,7 +92,7 @@ func (s *Server) buildRouter() {
|
|||||||
r.Delete("/{tx_id}", nil) // remove
|
r.Delete("/{tx_id}", nil) // remove
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Post("/invite", nil) // create a new invite link
|
s.Post("/invite/{hash}", nil) // create a new invite link
|
||||||
|
|
||||||
s.Route("/employees", func(r chi.Router) {
|
s.Route("/employees", func(r chi.Router) {
|
||||||
r.Get("/", nil) // list
|
r.Get("/", nil) // list
|
||||||
|
38
backend/internal/pkg/models/tx.go
Normal file
38
backend/internal/pkg/models/tx.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
Id uuid.UUID
|
||||||
|
|
||||||
|
Description string
|
||||||
|
OrganizationId uuid.UUID
|
||||||
|
CreatedBy *User
|
||||||
|
Amount int64
|
||||||
|
|
||||||
|
ToAddr []byte
|
||||||
|
|
||||||
|
MaxFeeAllowed int64
|
||||||
|
Deadline time.Time
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
|
||||||
|
ConfirmedAt time.Time
|
||||||
|
CancelledAt time.Time
|
||||||
|
|
||||||
|
CommitedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionConfirmation struct {
|
||||||
|
TxId uuid.UUID
|
||||||
|
User *User
|
||||||
|
OrganizationId uuid.UUID
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Confirmed bool
|
||||||
|
}
|
@ -1,34 +1,39 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type UserIdentity interface {
|
type UserIdentity interface {
|
||||||
Id() string
|
Id() uuid.UUID
|
||||||
Mnemonic() string
|
Seed() []byte
|
||||||
IsAdmin() bool
|
IsAdmin() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
id string
|
ID uuid.UUID
|
||||||
mnemonic string
|
Bip32Seed []byte
|
||||||
isAdmin bool
|
Admin bool
|
||||||
|
Activated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(id string, mnemonic string) *User {
|
func NewUser(id uuid.UUID, seed []byte) *User {
|
||||||
return &User{
|
return &User{
|
||||||
id: id,
|
ID: id,
|
||||||
mnemonic: mnemonic,
|
Bip32Seed: seed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Id() string {
|
func (u *User) Id() uuid.UUID {
|
||||||
return u.id
|
return u.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Mnemonic() string {
|
func (u *User) Seed() []byte {
|
||||||
return u.mnemonic
|
return u.Bip32Seed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) IsAdmin() bool {
|
func (u *User) IsAdmin() bool {
|
||||||
return u.isAdmin
|
return u.Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrganizationUser struct {
|
type OrganizationUser struct {
|
||||||
|
@ -21,7 +21,7 @@ type TransactionalStorage interface {
|
|||||||
Conn(ctx context.Context) DBTX
|
Conn(ctx context.Context) DBTX
|
||||||
}
|
}
|
||||||
|
|
||||||
type txCtxKey struct{}
|
type TxCtxKey struct{}
|
||||||
|
|
||||||
func Transaction(ctx context.Context, db *sql.DB, fn func(context.Context) error) error {
|
func Transaction(ctx context.Context, db *sql.DB, fn func(context.Context) error) error {
|
||||||
var err error
|
var err error
|
||||||
@ -70,14 +70,14 @@ func Transaction(ctx context.Context, db *sql.DB, fn func(context.Context) error
|
|||||||
return fmt.Errorf("error begin transaction. %w", err)
|
return fmt.Errorf("error begin transaction. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, txCtxKey{}, tx)
|
ctx = context.WithValue(ctx, TxCtxKey{}, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(ctx)
|
return fn(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasExternalTransaction(ctx context.Context) bool {
|
func hasExternalTransaction(ctx context.Context) bool {
|
||||||
if _, ok := ctx.Value(txCtxKey{}).(*sql.Tx); ok {
|
if _, ok := ctx.Value(TxCtxKey{}).(*sql.Tx); ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package smartcontract
|
||||||
|
|
||||||
|
type SmartContractInteractor interface {
|
||||||
|
}
|
316
backend/internal/usecase/repository/transactions/repository.go
Normal file
316
backend/internal/usecase/repository/transactions/repository.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package transactions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||||
|
sqltools "github.com/emochka2007/block-accounting/internal/pkg/sqlutils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetTransactionsFilter int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GetFilterExpired GetTransactionsFilter = iota
|
||||||
|
GetFilterNonExpired
|
||||||
|
GetFilterCancelled
|
||||||
|
GetFilterConfirmed
|
||||||
|
GetFilterCommited
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetTransactionsParams struct {
|
||||||
|
Ids uuid.UUIDs
|
||||||
|
OrganizationId uuid.UUID
|
||||||
|
CreatedById uuid.UUID
|
||||||
|
To []byte
|
||||||
|
Filters []GetTransactionsFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfirmTransactionParams struct {
|
||||||
|
TxId uuid.UUID
|
||||||
|
UserId uuid.UUID
|
||||||
|
OrganizationId uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelTransactionParams struct {
|
||||||
|
TxId uuid.UUID
|
||||||
|
UserId uuid.UUID
|
||||||
|
OrganizationId uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
GetTransactions(ctx context.Context, params GetTransactionsParams) ([]*models.Transaction, error)
|
||||||
|
CreateTransaction(ctx context.Context, tx models.Transaction) error
|
||||||
|
UpdateTransaction(ctx context.Context, tx models.Transaction) error
|
||||||
|
DeleteTransaction(ctx context.Context, tx models.Transaction) error
|
||||||
|
|
||||||
|
ConfirmTransaction(ctx context.Context, params ConfirmTransactionParams) error
|
||||||
|
CancelTransaction(ctx context.Context, params CancelTransactionParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type repositorySQL struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *repositorySQL) Conn(ctx context.Context) sqltools.DBTX {
|
||||||
|
if tx, ok := ctx.Value(sqltools.TxCtxKey{}).(*sql.Tx); ok {
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) GetTransactions(
|
||||||
|
ctx context.Context,
|
||||||
|
params GetTransactionsParams,
|
||||||
|
) ([]*models.Transaction, error) {
|
||||||
|
var txs []*models.Transaction = make([]*models.Transaction, 0, len(params.Ids))
|
||||||
|
err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) {
|
||||||
|
query := buildGetTransactionsQuery(params)
|
||||||
|
|
||||||
|
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetch transactions data from database. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if cErr := rows.Close(); cErr != nil {
|
||||||
|
err = errors.Join(fmt.Errorf("error close database rows. %w", cErr), err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
id uuid.UUID
|
||||||
|
description string
|
||||||
|
organizationId uuid.UUID
|
||||||
|
amount int64
|
||||||
|
toAddr []byte
|
||||||
|
maxFeeAllowed int64
|
||||||
|
deadline sql.NullTime
|
||||||
|
createdAt time.Time
|
||||||
|
updatedAt time.Time
|
||||||
|
confirmedAt sql.NullTime
|
||||||
|
cancelledAt sql.NullTime
|
||||||
|
commitedAt sql.NullTime
|
||||||
|
|
||||||
|
createdById uuid.UUID
|
||||||
|
createdBySeed []byte
|
||||||
|
createdByCreatedAt time.Time
|
||||||
|
createdByActivatedAt sql.NullTime
|
||||||
|
createdByIsAdmin bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = rows.Scan(
|
||||||
|
&id,
|
||||||
|
&description,
|
||||||
|
&organizationId,
|
||||||
|
&amount,
|
||||||
|
&toAddr,
|
||||||
|
&maxFeeAllowed,
|
||||||
|
&deadline,
|
||||||
|
&createdAt,
|
||||||
|
&updatedAt,
|
||||||
|
&confirmedAt,
|
||||||
|
&cancelledAt,
|
||||||
|
&commitedAt,
|
||||||
|
|
||||||
|
&createdById,
|
||||||
|
&createdBySeed,
|
||||||
|
&createdByCreatedAt,
|
||||||
|
&createdByActivatedAt,
|
||||||
|
&createdByIsAdmin,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("error scan row. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &models.Transaction{
|
||||||
|
Id: id,
|
||||||
|
Description: description,
|
||||||
|
OrganizationId: organizationId,
|
||||||
|
Amount: amount,
|
||||||
|
ToAddr: toAddr,
|
||||||
|
MaxFeeAllowed: maxFeeAllowed,
|
||||||
|
CreatedBy: &models.User{
|
||||||
|
ID: createdById,
|
||||||
|
Bip32Seed: createdBySeed,
|
||||||
|
},
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline.Valid {
|
||||||
|
tx.Deadline = deadline.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirmedAt.Valid {
|
||||||
|
tx.ConfirmedAt = confirmedAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
if commitedAt.Valid {
|
||||||
|
tx.CommitedAt = commitedAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
if cancelledAt.Valid {
|
||||||
|
tx.CancelledAt = cancelledAt.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdByActivatedAt.Valid {
|
||||||
|
tx.CreatedBy.Activated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error execute transactional operation. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return txs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) CreateTransaction(ctx context.Context, tx models.Transaction) error {
|
||||||
|
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error {
|
||||||
|
query := sq.Insert("transactions").Columns(
|
||||||
|
"t.id",
|
||||||
|
"t.description",
|
||||||
|
"t.organization_id",
|
||||||
|
"t.created_by",
|
||||||
|
"t.amount",
|
||||||
|
"t.to_addr",
|
||||||
|
"t.max_fee_allowed",
|
||||||
|
|
||||||
|
// todo insert later
|
||||||
|
// "t.deadline",
|
||||||
|
// "t.created_at",
|
||||||
|
// "t.updated_at",
|
||||||
|
// "t.confirmed_at",
|
||||||
|
// "t.cancelled_at",
|
||||||
|
// "t.commited_at",
|
||||||
|
).Values(
|
||||||
|
tx.Id,
|
||||||
|
tx.Description,
|
||||||
|
tx.OrganizationId,
|
||||||
|
tx.CreatedBy.ID,
|
||||||
|
tx.Amount,
|
||||||
|
tx.ToAddr,
|
||||||
|
tx.MaxFeeAllowed,
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo add optional insertions
|
||||||
|
|
||||||
|
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
|
||||||
|
return fmt.Errorf("error insert new transaction. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("error execute transactional operation. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) UpdateTransaction(ctx context.Context, tx models.Transaction) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) DeleteTransaction(ctx context.Context, tx models.Transaction) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) ConfirmTransaction(ctx context.Context, params ConfirmTransactionParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repositorySQL) CancelTransaction(ctx context.Context, params CancelTransactionParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildGetTransactionsQuery(params GetTransactionsParams) sq.SelectBuilder {
|
||||||
|
query := sq.Select(
|
||||||
|
`t.id,
|
||||||
|
t.description,
|
||||||
|
t.organization_id,
|
||||||
|
t.created_by,
|
||||||
|
t.amount,
|
||||||
|
t.to_addr,
|
||||||
|
t.max_fee_allowed,
|
||||||
|
t.deadline,
|
||||||
|
t.created_at,
|
||||||
|
t.updated_at,
|
||||||
|
t.confirmed_at,
|
||||||
|
t.cancelled_at,
|
||||||
|
t.commited_at,
|
||||||
|
|
||||||
|
u.id,
|
||||||
|
u.seed,
|
||||||
|
u.created_at,
|
||||||
|
u.activated_at,
|
||||||
|
u.is_admin`,
|
||||||
|
).From("transactions as t").
|
||||||
|
InnerJoin("users as u on u.id = t.created_by").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"t.organization_id": params.OrganizationId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(params.Ids) > 0 {
|
||||||
|
query = query.Where(sq.Eq{
|
||||||
|
"t.id": params.Ids,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.CreatedById != uuid.Nil {
|
||||||
|
query = query.Where(sq.Eq{
|
||||||
|
"t.created_by": params.CreatedById,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.OrganizationId != uuid.Nil {
|
||||||
|
query = query.Where(sq.Eq{
|
||||||
|
"t.organization_id": params.OrganizationId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.To != nil {
|
||||||
|
query = query.Where(sq.Eq{
|
||||||
|
"t.to_addr": params.To,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(params.Filters, GetFilterExpired) {
|
||||||
|
query = query.Where(sq.LtOrEq{
|
||||||
|
"t.deadline": time.Now(),
|
||||||
|
})
|
||||||
|
} else if slices.Contains(params.Filters, GetFilterNonExpired) {
|
||||||
|
query = query.Where(sq.GtOrEq{
|
||||||
|
"t.deadline": time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(params.Filters, GetFilterCancelled) {
|
||||||
|
query = query.Where(sq.NotEq{
|
||||||
|
"t.cancelled_at": nil,
|
||||||
|
})
|
||||||
|
} else if slices.Contains(params.Filters, GetFilterConfirmed) {
|
||||||
|
query = query.Where(sq.NotEq{
|
||||||
|
"t.confirmed_at": nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(params.Filters, GetFilterCommited) {
|
||||||
|
query = query.Where(sq.NotEq{
|
||||||
|
"t.commited_at": nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
74
backend/migrations/blockd.sql
Normal file
74
backend/migrations/blockd.sql
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
create table if not exists users (
|
||||||
|
id uuid not null,
|
||||||
|
seed bytea not null unique,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
activated_at timestamp default null,
|
||||||
|
primary key (id, seed)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists organizations (
|
||||||
|
id uuid primary key,
|
||||||
|
name varchar(300) default 'My Organization' not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists index_organizations_id
|
||||||
|
on organizations (id);
|
||||||
|
|
||||||
|
create table organizations_users (
|
||||||
|
organization_id uuid not null,
|
||||||
|
user_id uuid not null,
|
||||||
|
added_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
is_admin bool default false,
|
||||||
|
primary key(organization_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists index_organizations_users_organization_id_user_id_is_admin
|
||||||
|
on organizations_users (organization_id, user_id, is_admin);
|
||||||
|
|
||||||
|
create index if not exists index_organizations_users_organization_id_user_id
|
||||||
|
on organizations_users (organization_id, user_id);
|
||||||
|
|
||||||
|
create table if not exists transactions (
|
||||||
|
id uuid primary key,
|
||||||
|
description text default 'New Transaction',
|
||||||
|
organization_id uuid not null,
|
||||||
|
created_by uuid not null,
|
||||||
|
amount bigint default 0,
|
||||||
|
|
||||||
|
to_addr bytea not null,
|
||||||
|
|
||||||
|
max_fee_allowed bigint default 0,
|
||||||
|
deadline timestamp default null,
|
||||||
|
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
|
||||||
|
confirmed_at timestamp default null,
|
||||||
|
cancelled_at timestamp default null,
|
||||||
|
|
||||||
|
commited_at timestamp default null
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists index_transactions_id_organization_id
|
||||||
|
on transactions (organization_id);
|
||||||
|
|
||||||
|
create index if not exists index_transactions_id_organization_id_created_by
|
||||||
|
on transactions (organization_id, created_by);
|
||||||
|
|
||||||
|
create index if not exists index_transactions_organization_id_deadline
|
||||||
|
on transactions (organization_id, deadline);
|
||||||
|
|
||||||
|
create table transactions_confirmations (
|
||||||
|
tx_id uuid not null,
|
||||||
|
user_id uuid not null,
|
||||||
|
organization_id uuid not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
confirmed bool
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists index_transactions_confirmations_tx_id_user_id_organization_id
|
||||||
|
on transactions_confirmations (tx_id, user_id, organization_id);
|
Loading…
Reference in New Issue
Block a user