mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-04-04 13:46:27 +00:00
database schema updated, user hash indexes added, jwt interactor User method implemented, org user model implemented
This commit is contained in:
parent
86bd1107ce
commit
9f3b54ab51
@ -50,4 +50,119 @@ make run.local
|
||||
Just run
|
||||
``` sh
|
||||
make up
|
||||
```
|
||||
|
||||
# API
|
||||
Request content type: application/json
|
||||
Response content type: application/json
|
||||
|
||||
## POST **/join**
|
||||
### Request body:
|
||||
name (string, optional)
|
||||
credentals (object, optional)
|
||||
credentals.email (string, optional)
|
||||
credentals.phone (string, optional)
|
||||
credentals.telegram (string, optional)
|
||||
mnemonic (string, **required**)
|
||||
|
||||
### Example
|
||||
Request:
|
||||
``` bash
|
||||
curl --location 'http://localhost:8081/login' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Bladee The Grand Drainer",
|
||||
"credentals": {
|
||||
"email": "bladeee@gmail.com",
|
||||
"phone": "+79999999999",
|
||||
"telegram": "@thebladee"
|
||||
},
|
||||
"mnemonic":"airport donate language disagree dumb access insect tribe ozone humor foot jealous much digital confirm"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
``` json
|
||||
{
|
||||
"token": "token-here"
|
||||
}
|
||||
```
|
||||
|
||||
## POST **/login**
|
||||
### Request body:
|
||||
mnemonic (string, **required**)
|
||||
|
||||
### Example
|
||||
Request:
|
||||
``` bash
|
||||
curl --location 'http://localhost:8081/login' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"mnemonic":"airport donate language disagree dumb access insect tribe ozone humor foot jealous much digital confirm"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
``` json
|
||||
{
|
||||
"token": "token-here"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## POST **/join**
|
||||
### Request body:
|
||||
name (string, optional)
|
||||
credentals (object, optional)
|
||||
credentals.email (string, optional)
|
||||
credentals.phone (string, optional)
|
||||
credentals.telegram (string, optional)
|
||||
mnemonic (string, **required**)
|
||||
|
||||
### Example
|
||||
Request:
|
||||
``` bash
|
||||
curl --location 'http://localhost:8081/login' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Bladee The Grand Drainer",
|
||||
"credentals": {
|
||||
"email": "bladeee@gmail.com",
|
||||
"phone": "+79999999999",
|
||||
"telegram": "@thebladee"
|
||||
},
|
||||
"mnemonic":"airport donate language disagree dumb access insect tribe ozone humor foot jealous much digital confirm"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
``` json
|
||||
{
|
||||
"token": "token-here"
|
||||
}
|
||||
```
|
||||
|
||||
## POST **/organization**
|
||||
### Request body:
|
||||
name (string, **required**)
|
||||
address (string, optional)
|
||||
// org wallet address maybe??
|
||||
|
||||
### Example
|
||||
Request:
|
||||
``` bash
|
||||
curl --location 'http://localhost:8081/organization' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTU0NTY4Mzg4NTAsInVpZCI6ImI2NmU1Mjk4LTU1ZTctNGIxNy1hYzliLTA0MzU3YjBlN2Q0ZSJ9.K1I0QoZEdDYK_HEsJ0PdWOfZ8ugTcPfLqy7fHhvK9nk' \
|
||||
--data '{
|
||||
"name": "The Drain Gang Inc",
|
||||
"address": "Backsippestigen 22, 432 36 Varberg, Sweden"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
``` json
|
||||
{
|
||||
"id": "dfac7846-0f0a-11ef-9262-0242ac120002"
|
||||
}
|
||||
```
|
@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/interface/rest/domain"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/bip32"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/bip39"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -28,13 +28,13 @@ func main() {
|
||||
var reqc int
|
||||
|
||||
for {
|
||||
e, err := bip32.NewEntropy(256)
|
||||
e, err := bip39.NewEntropy(256)
|
||||
if err != nil {
|
||||
log.Println("ERROR: ", err)
|
||||
break
|
||||
}
|
||||
|
||||
m, err := bip32.NewMnemonic(e)
|
||||
m, err := bip39.NewMnemonic(e)
|
||||
if err != nil {
|
||||
log.Println("ERROR: ", err)
|
||||
break
|
||||
|
@ -19,6 +19,7 @@ services:
|
||||
depends_on:
|
||||
blockd-db:
|
||||
condition: service_healthy
|
||||
profiles: [blockd]
|
||||
|
||||
blockd-db:
|
||||
container_name: blockd-db
|
||||
@ -41,6 +42,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
profiles: [blockd, database]
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
@ -55,6 +57,7 @@ services:
|
||||
volumes:
|
||||
- ./prometheus:/etc/prometheus
|
||||
- prometheus_data:/prometheus
|
||||
profiles: [blockd, metrics]
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana
|
||||
@ -68,4 +71,5 @@ services:
|
||||
- GF_SECURITY_ADMIN_USER=admin
|
||||
- GF_SECURITY_ADMIN_PASSWORD=grafana
|
||||
volumes:
|
||||
- ./grafana:/etc/grafana/provisioning/datasources
|
||||
- ./grafana:/etc/grafana/provisioning/datasources
|
||||
profiles: [blockd, metrics]
|
@ -16,6 +16,6 @@ func provideUsersInteractor(
|
||||
return users.NewUsersInteractor(log.WithGroup("users-interactor"), usersRepo)
|
||||
}
|
||||
|
||||
func provideJWTInteractor(c config.Config) jwt.JWTInteractor {
|
||||
return jwt.NewWardenJWT(c.Common.JWTSecret)
|
||||
func provideJWTInteractor(c config.Config, usersInteractor users.UsersInteractor) jwt.JWTInteractor {
|
||||
return jwt.NewWardenJWT(c.Common.JWTSecret, usersInteractor)
|
||||
}
|
||||
|
@ -55,11 +55,13 @@ func provideAuthController(
|
||||
log *slog.Logger,
|
||||
usersInteractor users.UsersInteractor,
|
||||
authPresenter presenters.AuthPresenter,
|
||||
jwtInteractor jwt.JWTInteractor,
|
||||
) controllers.AuthController {
|
||||
return controllers.NewAuthController(
|
||||
log.WithGroup("auth-controller"),
|
||||
authPresenter,
|
||||
usersInteractor,
|
||||
jwtInteractor,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,9 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
usersInteractor := provideUsersInteractor(logger, repository)
|
||||
jwtInteractor := provideJWTInteractor(c)
|
||||
jwtInteractor := provideJWTInteractor(c, usersInteractor)
|
||||
authPresenter := provideAuthPresenter(jwtInteractor)
|
||||
authController := provideAuthController(logger, usersInteractor, authPresenter)
|
||||
authController := provideAuthController(logger, usersInteractor, authPresenter, jwtInteractor)
|
||||
rootController := provideControllers(logger, authController)
|
||||
server := provideRestServer(logger, rootController, c)
|
||||
serviceService := service.NewService(logger, server)
|
||||
|
@ -6,18 +6,20 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/bip32"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/bip39"
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/hdwallet"
|
||||
"github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt"
|
||||
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorAuthInvalidMnemonic = errors.New("Invalid Mnemonic")
|
||||
ErrorAuthInvalidMnemonic = errors.New("invalid mnemonic")
|
||||
ErrorTokenRequired = errors.New("token required")
|
||||
)
|
||||
|
||||
type AuthController interface {
|
||||
@ -38,11 +40,13 @@ func NewAuthController(
|
||||
log *slog.Logger,
|
||||
presenter presenters.AuthPresenter,
|
||||
usersInteractor users.UsersInteractor,
|
||||
jwtInteractor jwt.JWTInteractor,
|
||||
) AuthController {
|
||||
return &authController{
|
||||
log: log,
|
||||
presenter: presenter,
|
||||
usersInteractor: usersInteractor,
|
||||
jwtInteractor: jwtInteractor,
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +58,7 @@ func (c *authController) Join(w http.ResponseWriter, req *http.Request) error {
|
||||
|
||||
c.log.Debug("join request", slog.String("mnemonic", request.Mnemonic))
|
||||
|
||||
if !bip32.IsMnemonicValid(request.Mnemonic) {
|
||||
if !bip39.IsMnemonicValid(request.Mnemonic) {
|
||||
return fmt.Errorf("error invalid mnemonic. %w", ErrorAuthInvalidMnemonic)
|
||||
}
|
||||
|
||||
@ -111,6 +115,20 @@ func (c *authController) Login(w http.ResponseWriter, req *http.Request) error {
|
||||
// const mnemonicEntropyBitSize int = 256
|
||||
|
||||
func (c *authController) Invite(w http.ResponseWriter, req *http.Request) error {
|
||||
tokenStringRaw := req.Header.Get("Authorization")
|
||||
if tokenStringRaw == "" {
|
||||
return fmt.Errorf("error token requeired. %w", ErrorTokenRequired)
|
||||
}
|
||||
|
||||
tokenString := strings.Split(tokenStringRaw, " ")[1]
|
||||
|
||||
user, err := c.jwtInteractor.User(tokenString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetch user from token. %w", err)
|
||||
}
|
||||
|
||||
c.log.Debug("auth token", slog.Any("user", user))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,13 @@ import (
|
||||
)
|
||||
|
||||
type JoinRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Credentals struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Telegram string `json:"telegram,omitempty"`
|
||||
} `json:"credentals,omitempty"`
|
||||
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/interface/rest/controllers"
|
||||
"github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt"
|
||||
)
|
||||
|
||||
type apiError struct {
|
||||
@ -21,8 +22,17 @@ func buildApiError(code int, message string) apiError {
|
||||
|
||||
func mapError(err error) apiError {
|
||||
switch {
|
||||
// auth controller errors
|
||||
case errors.Is(err, controllers.ErrorAuthInvalidMnemonic):
|
||||
return buildApiError(http.StatusBadRequest, "Invalid Mnemonic")
|
||||
case errors.Is(err, controllers.ErrorTokenRequired):
|
||||
return buildApiError(http.StatusUnauthorized, "Token Required")
|
||||
|
||||
// jwt-related errors
|
||||
case errors.Is(err, jwt.ErrorTokenExpired):
|
||||
return buildApiError(http.StatusUnauthorized, "Token Expired")
|
||||
case errors.Is(err, jwt.ErrorInvalidTokenClaims):
|
||||
return buildApiError(http.StatusUnauthorized, "Invalid Token")
|
||||
default:
|
||||
return buildApiError(http.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
|
@ -77,30 +77,31 @@ func (s *Server) Close() {
|
||||
}
|
||||
|
||||
func (s *Server) buildRouter() {
|
||||
s.Mux = chi.NewRouter()
|
||||
router := chi.NewRouter()
|
||||
|
||||
s.Use(mw.Recoverer)
|
||||
s.Use(mw.RequestID)
|
||||
s.Use(s.handleMw)
|
||||
s.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
router.Use(mw.Recoverer)
|
||||
router.Use(mw.RequestID)
|
||||
router.Use(s.handleMw)
|
||||
router.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
|
||||
s.Get("/ping", s.handle(s.controllers.Ping.Ping, "ping"))
|
||||
router.Get("/ping", s.handle(s.controllers.Ping.Ping, "ping"))
|
||||
|
||||
// auth
|
||||
s.Post("/join", s.handle(s.controllers.Auth.Join, "join"))
|
||||
s.Post("/login", s.handle(s.controllers.Auth.Login, "login"))
|
||||
router.Post("/join", s.handle(s.controllers.Auth.Join, "join"))
|
||||
router.Post("/login", s.handle(s.controllers.Auth.Login, "login"))
|
||||
|
||||
s.Route("/organization/{organization_id}", func(r chi.Router) {
|
||||
s.Route("/transactions", func(r chi.Router) {
|
||||
router.Post("/organization", s.handle(s.controllers.Auth.Invite, "organization"))
|
||||
|
||||
router.Route("/organization/{organization_id}", func(r chi.Router) {
|
||||
router.Route("/transactions", func(r chi.Router) {
|
||||
r.Get("/", nil) // list
|
||||
r.Post("/", nil) // add
|
||||
r.Put("/{tx_id}", nil) // update / approve (or maybe body?)
|
||||
r.Delete("/{tx_id}", nil) // remove
|
||||
})
|
||||
|
||||
s.Post("/invite/{hash}", nil) // create a new invite link
|
||||
r.Post("/invite/{hash}", s.handle(s.controllers.Auth.Invite, "invite")) // create a new invite link
|
||||
|
||||
s.Route("/employees", func(r chi.Router) {
|
||||
r.Route("/employees", func(r chi.Router) {
|
||||
r.Get("/", nil) // list
|
||||
r.Post("/", nil) // add
|
||||
r.Put("/{employee_id}", nil) // update (or maybe body?)
|
||||
@ -108,6 +109,7 @@ func (s *Server) buildRouter() {
|
||||
})
|
||||
})
|
||||
|
||||
s.Mux = router
|
||||
}
|
||||
|
||||
func (s *Server) handle(
|
||||
|
@ -1,4 +1,4 @@
|
||||
package bip32
|
||||
package bip39
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
@ -1,4 +1,4 @@
|
||||
package bip32
|
||||
package bip39
|
||||
|
||||
import (
|
||||
"fmt"
|
14
backend/internal/pkg/models/organization.go
Normal file
14
backend/internal/pkg/models/organization.go
Normal file
@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
@ -9,28 +9,35 @@ import (
|
||||
type UserIdentity interface {
|
||||
Id() uuid.UUID
|
||||
Seed() []byte
|
||||
IsAdmin() bool
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Bip32Seed []byte
|
||||
Admin bool
|
||||
ID uuid.UUID
|
||||
|
||||
Name string
|
||||
|
||||
Credentails UserCredentials
|
||||
|
||||
Bip39Seed []byte
|
||||
Activated bool
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type UserCredentials struct {
|
||||
Email string
|
||||
Phone string
|
||||
Telegram string
|
||||
}
|
||||
|
||||
func NewUser(
|
||||
id uuid.UUID,
|
||||
seed []byte,
|
||||
isAdmin bool,
|
||||
activated bool,
|
||||
createdAt time.Time,
|
||||
) *User {
|
||||
return &User{
|
||||
ID: id,
|
||||
Bip32Seed: seed,
|
||||
Admin: isAdmin,
|
||||
Bip39Seed: seed,
|
||||
Activated: activated,
|
||||
CreatedAt: createdAt,
|
||||
}
|
||||
@ -41,14 +48,27 @@ func (u *User) Id() uuid.UUID {
|
||||
}
|
||||
|
||||
func (u *User) Seed() []byte {
|
||||
return u.Bip32Seed
|
||||
return u.Bip39Seed
|
||||
}
|
||||
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.Admin
|
||||
type OrganizationParticipant interface {
|
||||
UserIdentity
|
||||
|
||||
IsAdmin() bool
|
||||
Position() string
|
||||
}
|
||||
|
||||
type OrganizationUser struct {
|
||||
User
|
||||
// add org info
|
||||
|
||||
OrgPosition string
|
||||
Admin bool
|
||||
}
|
||||
|
||||
func (u *OrganizationUser) IsAdmin() bool {
|
||||
return u.Admin
|
||||
}
|
||||
|
||||
func (u *OrganizationUser) Position() string {
|
||||
return u.OrgPosition
|
||||
}
|
||||
|
@ -1,23 +1,37 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/pkg/models"
|
||||
"github.com/emochka2007/block-accounting/internal/usecase/interactors/users"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorInvalidTokenClaims = errors.New("invalid token claims")
|
||||
ErrorTokenExpired = errors.New("token expired")
|
||||
)
|
||||
|
||||
type JWTInteractor interface {
|
||||
NewToken(user models.UserIdentity, duration time.Duration) (string, error)
|
||||
User(token string) (*models.User, error)
|
||||
}
|
||||
|
||||
type jwtInteractor struct {
|
||||
secret []byte
|
||||
secret []byte
|
||||
usersInteractor users.UsersInteractor
|
||||
}
|
||||
|
||||
func NewWardenJWT(secret []byte) JWTInteractor {
|
||||
return &jwtInteractor{secret}
|
||||
func NewWardenJWT(secret []byte, usersInteractor users.UsersInteractor) JWTInteractor {
|
||||
return &jwtInteractor{
|
||||
secret: secret,
|
||||
usersInteractor: usersInteractor,
|
||||
}
|
||||
}
|
||||
|
||||
// NewToken creates new JWT token for given user
|
||||
@ -25,8 +39,8 @@ func (w *jwtInteractor) NewToken(user models.UserIdentity, duration time.Duratio
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
claims["uid"] = user.Id()
|
||||
claims["exp"] = time.Now().Add(duration).Unix()
|
||||
claims["uid"] = user.Id().String()
|
||||
claims["exp"] = time.Now().Add(duration).UnixMilli()
|
||||
|
||||
secret := w.secret
|
||||
|
||||
@ -37,3 +51,46 @@ func (w *jwtInteractor) NewToken(user models.UserIdentity, duration time.Duratio
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func (w *jwtInteractor) User(tokenStr string) (*models.User, error) {
|
||||
claims := make(jwt.MapClaims)
|
||||
|
||||
_, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
return w.secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Join(fmt.Errorf("error parse jwt token. %w", err), ErrorInvalidTokenClaims)
|
||||
}
|
||||
|
||||
if expDate, ok := claims["exp"].(float64); ok {
|
||||
if time.UnixMilli(int64(expDate)).Before(time.Now()) {
|
||||
return nil, fmt.Errorf("error token expired. %w", ErrorTokenExpired)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Join(fmt.Errorf("error parse exp date. %w", err), ErrorInvalidTokenClaims)
|
||||
}
|
||||
|
||||
var userIdString string
|
||||
var ok bool
|
||||
|
||||
if userIdString, ok = claims["uid"].(string); !ok {
|
||||
return nil, ErrorInvalidTokenClaims
|
||||
}
|
||||
|
||||
userId, err := uuid.Parse(userIdString)
|
||||
if err != nil {
|
||||
return nil, errors.Join(fmt.Errorf("error parse user id. %w", err), ErrorInvalidTokenClaims)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
users, err := w.usersInteractor.Get(ctx, users.GetParams{
|
||||
Ids: uuid.UUIDs{userId},
|
||||
})
|
||||
if err != nil || len(users) == 0 {
|
||||
return nil, fmt.Errorf("error fetch user from repository. %w", err)
|
||||
}
|
||||
|
||||
return users[0], nil
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package organizations
|
||||
|
||||
type OrganizationsInteractor interface {
|
||||
}
|
||||
|
||||
type organizationsInteractor struct {
|
||||
}
|
@ -72,7 +72,6 @@ func (i *usersInteractor) Create(ctx context.Context, params CreateParams) (*mod
|
||||
user := models.NewUser(
|
||||
uuid.New(),
|
||||
seed,
|
||||
params.IsAdmin,
|
||||
params.Activate,
|
||||
time.Now(),
|
||||
)
|
||||
|
@ -0,0 +1,80 @@
|
||||
package organizations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
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 GetParams struct {
|
||||
Ids uuid.UUIDs
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, org models.Organization) error
|
||||
Get(ctx context.Context, params GetParams) ([]*models.Organization, error)
|
||||
Update(ctx context.Context, org models.Organization) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
}
|
||||
|
||||
type repositorySQL struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *sql.DB) Repository {
|
||||
return &repositorySQL{
|
||||
db: 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) Create(ctx context.Context, org models.Organization) error {
|
||||
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) {
|
||||
query := sq.Insert("organizations").Columns(
|
||||
"id, name",
|
||||
).Values(
|
||||
org.ID,
|
||||
org.Name,
|
||||
)
|
||||
|
||||
if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil {
|
||||
return fmt.Errorf("error insert new organization. %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error execute transactional operation. %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Organization, error) {
|
||||
panic("implement me!")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *repositorySQL) Update(ctx context.Context, org models.Organization) error {
|
||||
panic("implement me!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repositorySQL) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
panic("implement me!")
|
||||
|
||||
return nil
|
||||
}
|
@ -58,6 +58,12 @@ type repositorySQL struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *sql.DB) Repository {
|
||||
return &repositorySQL{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *repositorySQL) Conn(ctx context.Context) sqltools.DBTX {
|
||||
if tx, ok := ctx.Value(sqltools.TxCtxKey).(*sql.Tx); ok {
|
||||
return tx
|
||||
@ -139,7 +145,7 @@ func (r *repositorySQL) GetTransactions(
|
||||
MaxFeeAllowed: maxFeeAllowed,
|
||||
CreatedBy: &models.User{
|
||||
ID: createdById,
|
||||
Bip32Seed: createdBySeed,
|
||||
Bip39Seed: createdBySeed,
|
||||
},
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
|
@ -55,7 +55,9 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
|
||||
if len(params.Ids) > 0 {
|
||||
query = query.Where("id", params.Ids)
|
||||
query = query.Where(sq.Eq{
|
||||
"id": params.Ids,
|
||||
})
|
||||
}
|
||||
|
||||
// if params.OrganizationId != uuid.Nil {
|
||||
@ -92,7 +94,7 @@ func (r *repositorySQL) Get(ctx context.Context, params GetParams) ([]*models.Us
|
||||
|
||||
users = append(users, &models.User{
|
||||
ID: id,
|
||||
Bip32Seed: seed,
|
||||
Bip39Seed: seed,
|
||||
//Admin: isAdmin,
|
||||
CreatedAt: createdAt,
|
||||
Activated: activatedAt.Valid,
|
||||
@ -113,7 +115,7 @@ func (r *repositorySQL) Create(ctx context.Context, user *models.User) error {
|
||||
|
||||
values := []any{
|
||||
user.ID,
|
||||
user.Bip32Seed,
|
||||
user.Bip39Seed,
|
||||
user.CreatedAt,
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,31 @@
|
||||
create table if not exists users (
|
||||
id uuid not null,
|
||||
name varchar(250),
|
||||
email varchar(200),
|
||||
phone varchar(16),
|
||||
tg varchar(200),
|
||||
seed bytea not null unique,
|
||||
created_at timestamp default current_timestamp,
|
||||
activated_at timestamp default null,
|
||||
primary key (id, seed)
|
||||
);
|
||||
|
||||
create index if not exists index_users_name
|
||||
on users using hash (name);
|
||||
|
||||
create index if not exists index_users_email
|
||||
on users using hash (email);
|
||||
|
||||
create index if not exists index_users_phone
|
||||
on users using hash (phone);
|
||||
|
||||
create index if not exists index_users_seed
|
||||
on users using hash (seed);
|
||||
|
||||
create table if not exists organizations (
|
||||
id uuid primary key,
|
||||
name varchar(300) default 'My Organization' not null,
|
||||
address varchar(750) default "",
|
||||
created_at timestamp default current_timestamp,
|
||||
updated_at timestamp default current_timestamp
|
||||
);
|
||||
@ -31,6 +48,17 @@ create index if not exists index_organizations_users_organization_id_user_id_is_
|
||||
create index if not exists index_organizations_users_organization_id_user_id
|
||||
on organizations_users (organization_id, user_id);
|
||||
|
||||
create table employees (
|
||||
id uuid primary key,
|
||||
organization_id uuid not null,
|
||||
wallet_address text not null,
|
||||
created_at timestamp default current_timestamp,
|
||||
updated_at timestamp default current_timestamp
|
||||
);
|
||||
|
||||
create index if not exists index_employees_id_organization_id
|
||||
on employees (id, organization_id);
|
||||
|
||||
create table if not exists transactions (
|
||||
id uuid primary key,
|
||||
description text default 'New Transaction',
|
||||
|
Loading…
Reference in New Issue
Block a user