diff --git a/backend/README.md b/backend/README.md index 9c9fc14..0c1b305 100644 --- a/backend/README.md +++ b/backend/README.md @@ -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" +} ``` \ No newline at end of file diff --git a/backend/cmd/load/main.go b/backend/cmd/load/main.go index 913672a..b910c2f 100644 --- a/backend/cmd/load/main.go +++ b/backend/cmd/load/main.go @@ -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 diff --git a/backend/docker-compose.yaml b/backend/docker-compose.yaml index c3eb496..1537cc6 100644 --- a/backend/docker-compose.yaml +++ b/backend/docker-compose.yaml @@ -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 \ No newline at end of file + - ./grafana:/etc/grafana/provisioning/datasources + profiles: [blockd, metrics] \ No newline at end of file diff --git a/backend/internal/factory/interactors.go b/backend/internal/factory/interactors.go index f3ea68c..24c3e47 100644 --- a/backend/internal/factory/interactors.go +++ b/backend/internal/factory/interactors.go @@ -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) } diff --git a/backend/internal/factory/interface.go b/backend/internal/factory/interface.go index b82c1ab..b8a479d 100644 --- a/backend/internal/factory/interface.go +++ b/backend/internal/factory/interface.go @@ -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, ) } diff --git a/backend/internal/factory/wire_gen.go b/backend/internal/factory/wire_gen.go index 9959819..88e2ebd 100644 --- a/backend/internal/factory/wire_gen.go +++ b/backend/internal/factory/wire_gen.go @@ -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) diff --git a/backend/internal/interface/rest/controllers/auth.go b/backend/internal/interface/rest/controllers/auth.go index ef9ce63..6bfe069 100644 --- a/backend/internal/interface/rest/controllers/auth.go +++ b/backend/internal/interface/rest/controllers/auth.go @@ -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 } diff --git a/backend/internal/interface/rest/domain/dto.go b/backend/internal/interface/rest/domain/dto.go index 48a22a5..da16644 100644 --- a/backend/internal/interface/rest/domain/dto.go +++ b/backend/internal/interface/rest/domain/dto.go @@ -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"` } diff --git a/backend/internal/interface/rest/errors.go b/backend/internal/interface/rest/errors.go index 6cb801f..adf8241 100644 --- a/backend/internal/interface/rest/errors.go +++ b/backend/internal/interface/rest/errors.go @@ -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") } diff --git a/backend/internal/interface/rest/server.go b/backend/internal/interface/rest/server.go index 8cba389..0041b2c 100644 --- a/backend/internal/interface/rest/server.go +++ b/backend/internal/interface/rest/server.go @@ -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( diff --git a/backend/internal/pkg/bip32/bip32.go b/backend/internal/pkg/bip39/bip39.go similarity index 99% rename from backend/internal/pkg/bip32/bip32.go rename to backend/internal/pkg/bip39/bip39.go index 9a74235..574c0e4 100644 --- a/backend/internal/pkg/bip32/bip32.go +++ b/backend/internal/pkg/bip39/bip39.go @@ -1,4 +1,4 @@ -package bip32 +package bip39 import ( "crypto/rand" diff --git a/backend/internal/pkg/bip32/wordslist.go b/backend/internal/pkg/bip39/wordslist.go similarity index 99% rename from backend/internal/pkg/bip32/wordslist.go rename to backend/internal/pkg/bip39/wordslist.go index 1a998d1..038412c 100644 --- a/backend/internal/pkg/bip32/wordslist.go +++ b/backend/internal/pkg/bip39/wordslist.go @@ -1,4 +1,4 @@ -package bip32 +package bip39 import ( "fmt" diff --git a/backend/internal/pkg/models/organization.go b/backend/internal/pkg/models/organization.go new file mode 100644 index 0000000..4967c4f --- /dev/null +++ b/backend/internal/pkg/models/organization.go @@ -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 +} diff --git a/backend/internal/pkg/models/user.go b/backend/internal/pkg/models/user.go index 812f6b3..d1556fa 100644 --- a/backend/internal/pkg/models/user.go +++ b/backend/internal/pkg/models/user.go @@ -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 } diff --git a/backend/internal/usecase/interactors/jwt/jwt.go b/backend/internal/usecase/interactors/jwt/jwt.go index 05bee4e..f8a7b7c 100644 --- a/backend/internal/usecase/interactors/jwt/jwt.go +++ b/backend/internal/usecase/interactors/jwt/jwt.go @@ -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 +} diff --git a/backend/internal/usecase/interactors/organizations/interactor.go b/backend/internal/usecase/interactors/organizations/interactor.go new file mode 100644 index 0000000..7870530 --- /dev/null +++ b/backend/internal/usecase/interactors/organizations/interactor.go @@ -0,0 +1,7 @@ +package organizations + +type OrganizationsInteractor interface { +} + +type organizationsInteractor struct { +} diff --git a/backend/internal/usecase/interactors/users/interactor.go b/backend/internal/usecase/interactors/users/interactor.go index 96908ce..eda11a3 100644 --- a/backend/internal/usecase/interactors/users/interactor.go +++ b/backend/internal/usecase/interactors/users/interactor.go @@ -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(), ) diff --git a/backend/internal/usecase/repository/organizations/repository.go b/backend/internal/usecase/repository/organizations/repository.go new file mode 100644 index 0000000..3ff6134 --- /dev/null +++ b/backend/internal/usecase/repository/organizations/repository.go @@ -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 +} diff --git a/backend/internal/usecase/repository/transactions/repository.go b/backend/internal/usecase/repository/transactions/repository.go index 76b9d69..ceac4eb 100644 --- a/backend/internal/usecase/repository/transactions/repository.go +++ b/backend/internal/usecase/repository/transactions/repository.go @@ -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, diff --git a/backend/internal/usecase/repository/users/repository.go b/backend/internal/usecase/repository/users/repository.go index eef530f..b060ae4 100644 --- a/backend/internal/usecase/repository/users/repository.go +++ b/backend/internal/usecase/repository/users/repository.go @@ -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, } diff --git a/backend/migrations/blockd.sql b/backend/migrations/blockd.sql index 5733637..b23f545 100644 --- a/backend/migrations/blockd.sql +++ b/backend/migrations/blockd.sql @@ -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',