satellite/referrals: set up referrals service and http endpoints (#3566)
This commit is contained in:
parent
17b057b33e
commit
79a4fff6c7
@ -6,10 +6,11 @@ package pb
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
math "math"
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
|
@ -296,6 +296,7 @@ func (planet *Planet) Shutdown() error {
|
||||
peer := &planet.peers[i]
|
||||
errlist.Add(peer.Close())
|
||||
}
|
||||
|
||||
for _, db := range planet.databases {
|
||||
errlist.Add(db.Close())
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package testplanet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@ -11,8 +12,14 @@ import (
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
"storj.io/storj/pkg/server"
|
||||
"storj.io/storj/private/testrand"
|
||||
)
|
||||
|
||||
// DefaultReferralManagerServer implements the default behavior of a mock referral manager
|
||||
type DefaultReferralManagerServer struct {
|
||||
tokenCount int
|
||||
}
|
||||
|
||||
// newReferralManager initializes a referral manager server
|
||||
func (planet *Planet) newReferralManager() (*server.Server, error) {
|
||||
prefix := "referralmanager"
|
||||
@ -64,3 +71,25 @@ func (planet *Planet) newReferralManager() (*server.Server, error) {
|
||||
log.Debug("id=" + identity.ID.String() + " addr=" + referralmanager.Addr().String())
|
||||
return referralmanager, nil
|
||||
}
|
||||
|
||||
// GetTokens implements a mock GetTokens endpoint that returns a number of referral tokens. By default, it returns 0 tokens.
|
||||
func (server *DefaultReferralManagerServer) GetTokens(ctx context.Context, req *pb.GetTokensRequest) (*pb.GetTokensResponse, error) {
|
||||
tokens := make([][]byte, server.tokenCount)
|
||||
for i := 0; i < server.tokenCount; i++ {
|
||||
uuid := testrand.UUID()
|
||||
tokens[i] = uuid[:]
|
||||
}
|
||||
return &pb.GetTokensResponse{
|
||||
TokenSecrets: tokens,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RedeemToken implements a mock RedeemToken endpoint.
|
||||
func (server *DefaultReferralManagerServer) RedeemToken(ctx context.Context, req *pb.RedeemTokenRequest) (*pb.RedeemTokenResponse, error) {
|
||||
return &pb.RedeemTokenResponse{}, nil
|
||||
}
|
||||
|
||||
// SetTokenCount sets the number of tokens GetTokens endpoint should return.
|
||||
func (server *DefaultReferralManagerServer) SetTokenCount(tokenCount int) {
|
||||
server.tokenCount = tokenCount
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import (
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/mockpayments"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/referrals"
|
||||
"storj.io/storj/satellite/repair/irreparable"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
"storj.io/storj/satellite/vouchers"
|
||||
@ -113,6 +114,10 @@ type API struct {
|
||||
Version *stripecoinpayments.VersionService
|
||||
}
|
||||
|
||||
Referrals struct {
|
||||
Service *referrals.Service
|
||||
}
|
||||
|
||||
Console struct {
|
||||
Listener net.Listener
|
||||
Service *console.Service
|
||||
@ -437,6 +442,15 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, pointerDB metai
|
||||
return nil, errs.New("Auth token secret required")
|
||||
}
|
||||
|
||||
peer.Referrals.Service = referrals.NewService(
|
||||
peer.Log.Named("referrals:service"),
|
||||
signing.SignerFromFullIdentity(peer.Identity),
|
||||
config.Referrals,
|
||||
peer.Dialer,
|
||||
peer.DB.Console().Users(),
|
||||
consoleConfig.PasswordCost,
|
||||
)
|
||||
|
||||
peer.Console.Service, err = console.NewService(
|
||||
peer.Log.Named("console:service"),
|
||||
&consoleauth.Hmac{Secret: []byte(consoleConfig.AuthTokenSecret)},
|
||||
@ -456,6 +470,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, pointerDB metai
|
||||
consoleConfig,
|
||||
peer.Console.Service,
|
||||
peer.Mail.Service,
|
||||
peer.Referrals.Service,
|
||||
peer.Console.Listener,
|
||||
config.Payments.StripeCoinPayments.StripePublicKey,
|
||||
)
|
||||
|
@ -89,7 +89,7 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
|
||||
PartnerID string `json:"partnerId"`
|
||||
Password string `json:"password"`
|
||||
SecretInput string `json:"secret"`
|
||||
ReferrerUserID string `json:"referrerUserID"`
|
||||
ReferrerUserID string `json:"referrerUserId"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(®isterData)
|
||||
|
149
satellite/console/consoleweb/consoleapi/referrals.go
Normal file
149
satellite/console/consoleweb/consoleapi/referrals.go
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package consoleapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/private/post"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/referrals"
|
||||
)
|
||||
|
||||
// ErrReferralsAPI - console referrals api error type.
|
||||
var ErrReferralsAPI = errs.Class("console referrals api error")
|
||||
|
||||
// Referrals is an api controller that exposes all referrals functionality.
|
||||
type Referrals struct {
|
||||
log *zap.Logger
|
||||
service *referrals.Service
|
||||
consoleService *console.Service
|
||||
mailService *mailservice.Service
|
||||
ExternalAddress string
|
||||
}
|
||||
|
||||
// NewReferrals is a constructor for api referrals controller.
|
||||
func NewReferrals(log *zap.Logger, service *referrals.Service, consoleService *console.Service, mailService *mailservice.Service, externalAddress string) *Referrals {
|
||||
return &Referrals{
|
||||
log: log,
|
||||
service: service,
|
||||
consoleService: consoleService,
|
||||
mailService: mailService,
|
||||
ExternalAddress: externalAddress,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTokens returns referral tokens based on user ID.
|
||||
func (controller *Referrals) GetTokens(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := console.GetAuth(ctx)
|
||||
if err != nil {
|
||||
controller.serveJSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tokens, err := controller.service.GetTokens(ctx, &auth.User.ID)
|
||||
if err != nil {
|
||||
controller.serveJSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(tokens)
|
||||
if err != nil {
|
||||
controller.log.Error("token handler could not encode token response", zap.Error(ErrReferralsAPI.Wrap(err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Register creates new user, sends activation e-mail.
|
||||
func (controller *Referrals) Register(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var registerData struct {
|
||||
FullName string `json:"fullName"`
|
||||
ShortName string `json:"shortName"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
ReferralToken string `json:"referralToken"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(®isterData)
|
||||
if err != nil {
|
||||
controller.serveJSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := controller.service.CreateUser(ctx, registerData)
|
||||
if err != nil {
|
||||
controller.serveJSONError(w, err)
|
||||
return
|
||||
}
|
||||
token, err := controller.consoleService.GenerateActivationToken(ctx, user.ID, user.Email)
|
||||
if err != nil {
|
||||
controller.serveJSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
link := controller.ExternalAddress + "activation/?token=" + token
|
||||
userName := user.ShortName
|
||||
if user.ShortName == "" {
|
||||
userName = user.FullName
|
||||
}
|
||||
|
||||
controller.mailService.SendRenderedAsync(
|
||||
ctx,
|
||||
[]post.Address{{Address: user.Email, Name: userName}},
|
||||
&consoleql.AccountActivationEmail{
|
||||
ActivationLink: link,
|
||||
Origin: controller.ExternalAddress,
|
||||
},
|
||||
)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(user.ID)
|
||||
if err != nil {
|
||||
controller.log.Error("registration handler could not encode userID", zap.Error(ErrReferralsAPI.Wrap(err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// serveJSONError writes JSON error to response output stream.
|
||||
func (controller *Referrals) serveJSONError(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(controller.getStatusCode(err))
|
||||
|
||||
var response struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
response.Error = err.Error()
|
||||
|
||||
err = json.NewEncoder(w).Encode(response)
|
||||
if err != nil {
|
||||
controller.log.Error("failed to write json error response", zap.Error(ErrReferralsAPI.Wrap(err)))
|
||||
}
|
||||
}
|
||||
|
||||
// getStatusCode returns http.StatusCode depends on console error class.
|
||||
func (controller *Referrals) getStatusCode(err error) int {
|
||||
switch {
|
||||
case console.ErrValidation.Has(err):
|
||||
return http.StatusBadRequest
|
||||
case console.ErrUnauthorized.Has(err):
|
||||
return http.StatusUnauthorized
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/referrals"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -77,9 +78,10 @@ type Config struct {
|
||||
type Server struct {
|
||||
log *zap.Logger
|
||||
|
||||
config Config
|
||||
service *console.Service
|
||||
mailService *mailservice.Service
|
||||
config Config
|
||||
service *console.Service
|
||||
mailService *mailservice.Service
|
||||
referralsService *referrals.Service
|
||||
|
||||
listener net.Listener
|
||||
server http.Server
|
||||
@ -99,14 +101,15 @@ type Server struct {
|
||||
}
|
||||
|
||||
// NewServer creates new instance of console server.
|
||||
func NewServer(logger *zap.Logger, config Config, service *console.Service, mailService *mailservice.Service, listener net.Listener, stripePublicKey string) *Server {
|
||||
func NewServer(logger *zap.Logger, config Config, service *console.Service, mailService *mailservice.Service, referralsService *referrals.Service, listener net.Listener, stripePublicKey string) *Server {
|
||||
server := Server{
|
||||
log: logger,
|
||||
config: config,
|
||||
listener: listener,
|
||||
service: service,
|
||||
mailService: mailService,
|
||||
stripePublicKey: stripePublicKey,
|
||||
log: logger,
|
||||
config: config,
|
||||
listener: listener,
|
||||
service: service,
|
||||
mailService: mailService,
|
||||
referralsService: referralsService,
|
||||
stripePublicKey: stripePublicKey,
|
||||
}
|
||||
|
||||
logger.Sugar().Debugf("Starting Satellite UI on %s...", server.listener.Addr().String())
|
||||
@ -126,6 +129,11 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
||||
router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
|
||||
router.HandleFunc("/robots.txt", server.seoHandler)
|
||||
|
||||
referralsController := consoleapi.NewReferrals(logger, referralsService, service, mailService, server.config.ExternalAddress)
|
||||
referralsRouter := router.PathPrefix("/api/v0/referrals").Subrouter()
|
||||
referralsRouter.Handle("/tokens", server.withAuth(http.HandlerFunc(referralsController.GetTokens))).Methods(http.MethodGet)
|
||||
referralsRouter.HandleFunc("/register", referralsController.Register).Methods(http.MethodPost)
|
||||
|
||||
authController := consoleapi.NewAuth(logger, service, mailService, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
|
||||
authRouter := router.PathPrefix("/api/v0/auth").Subrouter()
|
||||
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet)
|
||||
|
@ -495,7 +495,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, passwor
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validatePassword(password); err != nil {
|
||||
if err := ValidatePassword(password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -598,7 +598,7 @@ func (s *Service) UpdateAccount(ctx context.Context, fullName string, shortName
|
||||
}
|
||||
|
||||
// validate fullName
|
||||
err = validateFullName(fullName)
|
||||
err = ValidateFullName(fullName)
|
||||
if err != nil {
|
||||
return ErrValidation.Wrap(err)
|
||||
}
|
||||
@ -635,7 +635,7 @@ func (s *Service) ChangePassword(ctx context.Context, pass, newPass string) (err
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
if err := validatePassword(newPass); err != nil {
|
||||
if err := ValidatePassword(newPass); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ func (user *UserInfo) IsValid() error {
|
||||
var errs validationErrors
|
||||
|
||||
// validate fullName
|
||||
if err := validateFullName(user.FullName); err != nil {
|
||||
if err := ValidateFullName(user.FullName); err != nil {
|
||||
errs.AddWrap(err)
|
||||
}
|
||||
|
||||
@ -58,8 +58,8 @@ type CreateUser struct {
|
||||
func (user *CreateUser) IsValid() error {
|
||||
var errs validationErrors
|
||||
|
||||
errs.AddWrap(validateFullName(user.FullName))
|
||||
errs.AddWrap(validatePassword(user.Password))
|
||||
errs.AddWrap(ValidateFullName(user.FullName))
|
||||
errs.AddWrap(ValidatePassword(user.Password))
|
||||
|
||||
// validate email
|
||||
_, err := mail.ParseAddress(user.Email)
|
||||
|
@ -32,8 +32,8 @@ func (validation *validationErrors) Combine() error {
|
||||
return errs.Combine(*validation...)
|
||||
}
|
||||
|
||||
// validatePassword validates password
|
||||
func validatePassword(pass string) error {
|
||||
// ValidatePassword validates password
|
||||
func ValidatePassword(pass string) error {
|
||||
var errs validationErrors
|
||||
|
||||
if len(pass) < passMinLength {
|
||||
@ -43,8 +43,8 @@ func validatePassword(pass string) error {
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
// validateFullName validates full name.
|
||||
func validateFullName(name string) error {
|
||||
// ValidateFullName validates full name.
|
||||
func ValidateFullName(name string) error {
|
||||
if name == "" {
|
||||
return errs.New("full name can not be empty")
|
||||
}
|
||||
|
@ -3,9 +3,197 @@
|
||||
|
||||
package referrals
|
||||
|
||||
import "storj.io/storj/pkg/storj"
|
||||
import (
|
||||
"context"
|
||||
|
||||
// Config for referrals service.
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/spacemonkeygo/monkit.v2"
|
||||
|
||||
"storj.io/storj/pkg/pb"
|
||||
"storj.io/storj/pkg/rpc"
|
||||
"storj.io/storj/pkg/signing"
|
||||
"storj.io/storj/pkg/storj"
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
var mon = monkit.Package()
|
||||
|
||||
var (
|
||||
// ErrUsedEmail is an error class for reporting already used emails.
|
||||
ErrUsedEmail = errs.Class("email used error")
|
||||
)
|
||||
|
||||
// Config is for referrals service.
|
||||
type Config struct {
|
||||
ReferralManagerURL storj.NodeURL
|
||||
}
|
||||
|
||||
// Service allows communicating with the Referral Manager
|
||||
//
|
||||
// architecture: Service
|
||||
type Service struct {
|
||||
log *zap.Logger
|
||||
signer signing.Signer
|
||||
config Config
|
||||
dialer rpc.Dialer
|
||||
db console.Users
|
||||
passwordCost int
|
||||
}
|
||||
|
||||
// NewService returns a service for handling referrals information.
|
||||
func NewService(log *zap.Logger, signer signing.Signer, config Config, dialer rpc.Dialer, db console.Users, passwordCost int) *Service {
|
||||
return &Service{
|
||||
log: log,
|
||||
signer: signer,
|
||||
config: config,
|
||||
dialer: dialer,
|
||||
db: db,
|
||||
passwordCost: passwordCost,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTokens returns tokens based on user ID.
|
||||
func (service *Service) GetTokens(ctx context.Context, userID *uuid.UUID) (tokens []uuid.UUID, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if userID.IsZero() {
|
||||
return nil, errs.New("user ID is not defined")
|
||||
}
|
||||
|
||||
conn, err := service.referralManagerConn(ctx)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = conn.Close()
|
||||
}()
|
||||
|
||||
client := conn.ReferralManagerClient()
|
||||
response, err := client.GetTokens(ctx, &pb.GetTokensRequest{
|
||||
OwnerUserId: userID[:],
|
||||
OwnerSatelliteId: service.signer.ID(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
tokensInBytes := response.GetTokenSecrets()
|
||||
if tokensInBytes != nil && len(tokensInBytes) == 0 {
|
||||
return nil, errs.New("no available tokens")
|
||||
}
|
||||
|
||||
tokens = make([]uuid.UUID, len(tokensInBytes))
|
||||
for i := range tokensInBytes {
|
||||
token, err := bytesToUUID(tokensInBytes[i])
|
||||
if err != nil {
|
||||
service.log.Debug("failed to convert bytes to UUID", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
tokens[i] = token
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// CreateUser validates user's registration information and creates a new user.
|
||||
func (service *Service) CreateUser(ctx context.Context, user CreateUser) (_ *console.User, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if err := user.IsValid(); err != nil {
|
||||
return nil, ErrValidation.Wrap(err)
|
||||
}
|
||||
|
||||
if len(user.ReferralToken) == 0 {
|
||||
return nil, errs.New("referral token is not defined")
|
||||
}
|
||||
|
||||
_, err = service.db.GetByEmail(ctx, user.Email)
|
||||
if err == nil {
|
||||
return nil, ErrUsedEmail.New("")
|
||||
}
|
||||
|
||||
userID, err := uuid.New()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
err = service.redeemToken(ctx, userID, user.ReferralToken)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), service.passwordCost)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
newUser := &console.User{
|
||||
ID: *userID,
|
||||
Email: user.Email,
|
||||
FullName: user.FullName,
|
||||
ShortName: user.ShortName,
|
||||
PasswordHash: hash,
|
||||
}
|
||||
|
||||
u, err := service.db.Insert(ctx,
|
||||
newUser,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (service *Service) redeemToken(ctx context.Context, userID *uuid.UUID, token string) error {
|
||||
conn, err := service.referralManagerConn(ctx)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
defer func() {
|
||||
err = conn.Close()
|
||||
}()
|
||||
|
||||
if userID.IsZero() || len(token) == 0 {
|
||||
return errs.New("invalid argument")
|
||||
}
|
||||
|
||||
referralToken, err := uuid.Parse(token)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
client := conn.ReferralManagerClient()
|
||||
_, err = client.RedeemToken(ctx, &pb.RedeemTokenRequest{
|
||||
Token: referralToken[:],
|
||||
RedeemUserId: userID[:],
|
||||
RedeemSatelliteId: service.signer.ID(),
|
||||
})
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) referralManagerConn(ctx context.Context) (*rpc.Conn, error) {
|
||||
if service.config.ReferralManagerURL.IsZero() {
|
||||
return nil, errs.New("missing referral manager url configuration")
|
||||
}
|
||||
|
||||
return service.dialer.DialAddressID(ctx, service.config.ReferralManagerURL.Address, service.config.ReferralManagerURL.ID)
|
||||
}
|
||||
|
||||
// bytesToUUID is used to convert []byte to UUID
|
||||
func bytesToUUID(data []byte) (uuid.UUID, error) {
|
||||
var id uuid.UUID
|
||||
|
||||
copy(id[:], data)
|
||||
if len(id) != len(data) {
|
||||
return uuid.UUID{}, errs.New("Invalid uuid")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
93
satellite/referrals/service_test.go
Normal file
93
satellite/referrals/service_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package referrals_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/pkg/pb"
|
||||
"storj.io/storj/pkg/rpc/rpcstatus"
|
||||
"storj.io/storj/private/testcontext"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/private/testrand"
|
||||
"storj.io/storj/satellite/referrals"
|
||||
)
|
||||
|
||||
func TestServiceSuccess(t *testing.T) {
|
||||
endpoint := &endpointHappyPath{}
|
||||
tokenCount := 2
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
ReferralManagerServer: func(logger *zap.Logger) pb.ReferralManagerServer {
|
||||
endpoint.SetTokenCount(tokenCount)
|
||||
return endpoint
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
|
||||
satellite := planet.Satellites[0]
|
||||
|
||||
userID := testrand.UUID()
|
||||
tokens, err := satellite.API.Referrals.Service.GetTokens(ctx, &userID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tokens, tokenCount)
|
||||
|
||||
user := referrals.CreateUser{
|
||||
FullName: "test",
|
||||
ShortName: "test",
|
||||
Email: "test@mail.test",
|
||||
Password: "123a123",
|
||||
ReferralToken: testrand.UUID().String(),
|
||||
}
|
||||
|
||||
createdUser, err := satellite.API.Referrals.Service.CreateUser(ctx, user)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, user.Email, createdUser.Email)
|
||||
require.Equal(t, user.FullName, createdUser.FullName)
|
||||
require.Equal(t, user.ShortName, createdUser.ShortName)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServiceRedeemFailure(t *testing.T) {
|
||||
endpoint := &endpointFailedPath{}
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
ReferralManagerServer: func(logger *zap.Logger) pb.ReferralManagerServer {
|
||||
endpoint.SetTokenCount(2)
|
||||
return endpoint
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
|
||||
satellite := planet.Satellites[0]
|
||||
|
||||
user := referrals.CreateUser{
|
||||
FullName: "test",
|
||||
ShortName: "test",
|
||||
Email: "test@mail.test",
|
||||
Password: "123a123",
|
||||
ReferralToken: testrand.UUID().String(),
|
||||
}
|
||||
_, err := satellite.API.Referrals.Service.CreateUser(ctx, user)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
type endpointHappyPath struct {
|
||||
testplanet.DefaultReferralManagerServer
|
||||
}
|
||||
|
||||
type endpointFailedPath struct {
|
||||
testplanet.DefaultReferralManagerServer
|
||||
}
|
||||
|
||||
func (endpoint *endpointFailedPath) RedeemToken(ctx context.Context, req *pb.RedeemTokenRequest) (*pb.RedeemTokenResponse, error) {
|
||||
return nil, rpcstatus.Error(rpcstatus.NotFound, "")
|
||||
}
|
46
satellite/referrals/users.go
Normal file
46
satellite/referrals/users.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package referrals
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/satellite/console"
|
||||
)
|
||||
|
||||
// ErrValidation validation related error class
|
||||
var ErrValidation = errs.Class("validation error")
|
||||
|
||||
// CreateUser contains information that's necessary for creating a new user through referral program
|
||||
type CreateUser struct {
|
||||
FullName string `json:"fullName"`
|
||||
ShortName string `json:"shortName"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
ReferralToken string `json:"referralToken"`
|
||||
}
|
||||
|
||||
// IsValid checks CreateUser validity and returns error describing whats wrong.
|
||||
func (user *CreateUser) IsValid() error {
|
||||
var errors []error
|
||||
|
||||
errors = append(errors, console.ValidateFullName(user.FullName))
|
||||
errors = append(errors, console.ValidatePassword(user.Password))
|
||||
|
||||
// validate email
|
||||
_, err := mail.ParseAddress(user.Email)
|
||||
errors = append(errors, err)
|
||||
|
||||
if user.ReferralToken != "" {
|
||||
_, err := uuid.Parse(user.ReferralToken)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs.Combine(errors...)
|
||||
}
|
Loading…
Reference in New Issue
Block a user