mirror of
https://github.com/foomo/shop.git
synced 2025-10-16 12:35:39 +00:00
customer soft lock improved
customer version history dropped
This commit is contained in:
parent
d4c84c5721
commit
ebdd0e529f
@ -19,12 +19,10 @@ var (
|
||||
MONGO_URL = "mongodb://" + WithDocker
|
||||
MONGO_URL_PRODUCTS = "mongodb://" + ProductsLocal
|
||||
|
||||
MONGO_COLLECTION_CREDENTIALS = "credentials"
|
||||
MONGO_COLLECTION_ORDERS = "orders"
|
||||
MONGO_COLLECTION_ORDERS_HISTORY = "orders_history"
|
||||
MONGO_COLLECTION_CUSTOMERS_HISTORY = "crm_customer_cache_history"
|
||||
MONGO_COLLECTION_CUSTOMERS = "crm_customer_cache"
|
||||
MONGO_COLLECTION_WATCHLISTS = "watchlists"
|
||||
MONGO_COLLECTION_ORDERS = "orders"
|
||||
MONGO_COLLECTION_ORDERS_HISTORY = "orders_history"
|
||||
MONGO_COLLECTION_CUSTOMERS = "customerscrm"
|
||||
MONGO_COLLECTION_WATCHLISTS = "watchlists"
|
||||
|
||||
MONGO_COLLECTION_PRICERULES = "pricerules"
|
||||
MONGO_COLLECTION_PRICERULES_VOUCHERS = "pricerules_vouchers"
|
||||
|
||||
@ -5,9 +5,10 @@ package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
zxcbvn "github.com/nbutton23/zxcvbn-go"
|
||||
"github.com/nbutton23/zxcvbn-go/scoring"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var minLength int = -1
|
||||
@ -22,7 +23,7 @@ func SetMaxLength(max int) {
|
||||
|
||||
// DeterminePasswordStrength returns a detailed info about the strength of the given password
|
||||
// @userInput e.g. user name. Given strings are matched against password to prohibit similarities between username and password
|
||||
func DeterminePasswordStrength(password string, userInput []string) scoring.MinEntropyMatch {
|
||||
func determinePasswordStrength(password string, userInput []string) scoring.MinEntropyMatch {
|
||||
return zxcbvn.PasswordStrength(password, userInput)
|
||||
}
|
||||
|
||||
@ -34,8 +35,6 @@ func GetPasswordScore(password string, userInput []string) (int, error) {
|
||||
if maxLength != -1 && len(password) > maxLength {
|
||||
return 0, errors.New("Password must be not longer than " + strconv.Itoa(maxLength) + " characters!")
|
||||
}
|
||||
match := DeterminePasswordStrength(password, userInput)
|
||||
match := determinePasswordStrength(password, userInput)
|
||||
return match.Score, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -11,13 +11,13 @@ func TestCryptoPasswordStrength(t *testing.T) {
|
||||
passwords := []string{"mypassword", "summertablecactus+", "osome+#,,brassford"}
|
||||
for _, password := range passwords {
|
||||
fmt.Println("---------- Password:", password, "-------------")
|
||||
utils.PrintJSON(DeterminePasswordStrength(password, nil))
|
||||
utils.PrintJSON(determinePasswordStrength(password, nil))
|
||||
}
|
||||
|
||||
fmt.Println("---------- Password with userInput-------------")
|
||||
// Test with user input, expected score 1
|
||||
DeterminePasswordStrength(passwords[1], []string{"Table"})
|
||||
utils.PrintJSON(DeterminePasswordStrength(passwords[1], []string{"Table"}))
|
||||
determinePasswordStrength(passwords[1], []string{"Table"})
|
||||
utils.PrintJSON(determinePasswordStrength(passwords[1], []string{"Table"}))
|
||||
|
||||
//for i := 0; i < 1000; i++ {
|
||||
for _, password := range passwords {
|
||||
|
||||
@ -1,171 +0,0 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/shop/crypto"
|
||||
"github.com/foomo/shop/shop_error"
|
||||
"github.com/foomo/shop/version"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// ~ PUBLIC TYPES
|
||||
//------------------------------------------------------------------
|
||||
|
||||
type CustomerCredentials struct {
|
||||
BsonId bson.ObjectId `bson:"_id,omitempty"`
|
||||
Version *version.Version
|
||||
Email string // always stored lowercase
|
||||
Crypto *crypto.Crypto
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// ~ PUBLIC METHODS
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// GetCredentials from db
|
||||
func GetCredentials(email string) (*CustomerCredentials, error) {
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
credentials := &CustomerCredentials{}
|
||||
err := collection.Find(&bson.M{"email": lc(email)}).One(credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// CreateCustomerCredentials
|
||||
func CreateCustomerCredentials(email, password string) error {
|
||||
if password == "" {
|
||||
log.Println("WARNING: Empty password is reserved for guest customer (and will not grant access).")
|
||||
}
|
||||
available, err := CheckLoginAvailable(lc(email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !available {
|
||||
return errors.New(lc(email) + " is already taken!")
|
||||
}
|
||||
crypto, err := crypto.HashPassword(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credentials := &CustomerCredentials{
|
||||
Version: version.NewVersion(),
|
||||
Email: lc(email),
|
||||
Crypto: crypto,
|
||||
}
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
return collection.Insert(credentials)
|
||||
|
||||
}
|
||||
|
||||
// CheckLoginAvailable returns true if the email address is available as login credential (ignores guest accounts)
|
||||
func CheckLoginAvailable(email string) (bool, error) {
|
||||
session, collection := GetCustomerPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
query := collection.Find(&bson.M{
|
||||
"$and": []*bson.M{
|
||||
&bson.M{"email": lc(email)},
|
||||
&bson.M{"isguest": false},
|
||||
}})
|
||||
|
||||
count, err := query.Count()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count == 0, nil
|
||||
}
|
||||
|
||||
// CheckLoginCredentials returns true if customer with email exists and password matches with the hash stores in customers Crypto.
|
||||
// Email is not case-sensitive to avoid user frustration
|
||||
func CheckLoginCredentials(email, password string) (bool, error) {
|
||||
if password == "" {
|
||||
log.Println("INFO: No access granted, because password is empty. (Empty password is used for guest users)")
|
||||
return false, nil
|
||||
}
|
||||
credentials, err := GetCredentials(lc(email))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return crypto.VerifyPassword(credentials.Crypto, password), nil
|
||||
}
|
||||
|
||||
// ChangePassword changes the password of the user.
|
||||
// If force, passworldOld is irrelevant and the password is changed in any case.
|
||||
func ChangePassword(email, password, passwordNew string, force bool) error {
|
||||
credentials, err := GetCredentials(lc(email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auth := force || crypto.VerifyPassword(credentials.Crypto, password)
|
||||
if auth {
|
||||
newCrypto, err := crypto.HashPassword(passwordNew)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credentials.Crypto = newCrypto
|
||||
credentials.Version.Increment()
|
||||
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
_, err = collection.UpsertId(credentials.BsonId, credentials)
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.New("Authorization Error: Could not change password.")
|
||||
}
|
||||
|
||||
func ChangeEmail(email, newEmail string) error {
|
||||
available, err := CheckLoginAvailable(lc(newEmail))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !available {
|
||||
return errors.New("Could not change Email: \"" + lc(newEmail) + "\" is already taken!")
|
||||
}
|
||||
credentials, err := GetCredentials(lc(email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credentials.Email = lc(newEmail)
|
||||
credentials.Version.Increment()
|
||||
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
_, err = collection.UpsertId(credentials.BsonId, credentials)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteCredential(email string) error {
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
// remove credentials
|
||||
err := collection.Remove(&bson.M{"email": lc(email)})
|
||||
if err != nil && err.Error() != shop_error.ErrorNotInDatabase {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// ~ PRIVATE METHODS
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// lc returns lowercase version of string
|
||||
func lc(s string) string {
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/shop/test_utils"
|
||||
)
|
||||
|
||||
func TestCredentials(t *testing.T) {
|
||||
|
||||
test_utils.DropAllCollections()
|
||||
|
||||
// Create credentials for a user
|
||||
email := "foo@bar.com"
|
||||
password := "123456"
|
||||
err := CreateCustomerCredentials(email, password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create credentials for another user
|
||||
email = "alice@bar.com"
|
||||
password = "wonderland"
|
||||
err = CreateCustomerCredentials(email, password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ! ----------------------------------------------------------------------
|
||||
// ~ due to non unique mails creating multiple creds with same mail is allowed now
|
||||
// ! ----------------------------------------------------------------------
|
||||
|
||||
// // Try to create credentials for already taken email.
|
||||
// // This should fail
|
||||
// email = "alice@bar.com"
|
||||
// password = "wonderland"
|
||||
// err = CreateCustomerCredentials(email, password)
|
||||
// if err == nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// Change email
|
||||
err = ChangeEmail("foo@bar.com", "trent@bar.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Try to change email that does not exist.
|
||||
err = ChangeEmail("idont@exist.com", "a@b.com")
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try to change email with incorrect password
|
||||
err = ChangePassword("alice@bar.com", "wrong", "myNewPassWord", false)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Try to change email with correct password
|
||||
err = ChangePassword("alice@bar.com", "wonderland", "myNewPassWord", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Try new Password
|
||||
auth, err := CheckLoginCredentials("alice@bar.com", "myNewPassWord")
|
||||
if !auth || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Delete Credentials of trent@bar.com
|
||||
err = DeleteCredential("trent@bar.com")
|
||||
}
|
||||
|
||||
func TestCredentialsForGuestCustomer(t *testing.T) {
|
||||
|
||||
test_utils.DropAllCollections()
|
||||
// Create credentials for a user
|
||||
email := "foo@bar.com"
|
||||
password := ""
|
||||
err := CreateCustomerCredentials(email, password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
auth, err := CheckLoginCredentials(email, password)
|
||||
if auth || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/shop/address"
|
||||
@ -53,7 +53,6 @@ type Customer struct {
|
||||
ExternalID string
|
||||
Id string
|
||||
unlinkDB bool // if true, changes to Customer are not stored in database
|
||||
Flags *Flags
|
||||
Version *version.Version
|
||||
CreatedAt time.Time
|
||||
LastModifiedAt time.Time
|
||||
@ -68,10 +67,6 @@ type Customer struct {
|
||||
Custom interface{}
|
||||
}
|
||||
|
||||
type Flags struct {
|
||||
forceUpsert bool // if true, Upsert is performed even if there is a version conflict. This is important for rollbacks.
|
||||
}
|
||||
|
||||
type Company struct {
|
||||
Name string
|
||||
Type string
|
||||
@ -126,7 +121,6 @@ func NewCustomer(addrkey string, addrkeyHash string, externalID string, mailCont
|
||||
|
||||
email := lc(mailContact.Value)
|
||||
customer := &Customer{
|
||||
Flags: &Flags{},
|
||||
Version: version.NewVersion(),
|
||||
Id: unique.GetNewID(),
|
||||
ExternalID: externalID,
|
||||
@ -148,6 +142,9 @@ func NewCustomer(addrkey string, addrkeyHash string, externalID string, mailCont
|
||||
Custom: customProvider.NewCustomerCustom(),
|
||||
}
|
||||
|
||||
// initial version should be 1
|
||||
customer.Version.Increment()
|
||||
|
||||
// persist customer in database
|
||||
errInsert := customer.insert()
|
||||
if errInsert != nil {
|
||||
@ -163,40 +160,6 @@ func NewCustomer(addrkey string, addrkeyHash string, externalID string, mailCont
|
||||
// ~ PUBLIC METHODS ON CUSTOMER
|
||||
//------------------------------------------------------------------
|
||||
|
||||
func (customer *Customer) ChangeEmail(email, newEmail string) error {
|
||||
// lower case
|
||||
email = lc(email)
|
||||
newEmail = lc(newEmail)
|
||||
|
||||
customer.Email = newEmail
|
||||
for _, addr := range customer.GetAddresses() {
|
||||
for _, contact := range addr.Person.Contacts {
|
||||
if contact.IsMail() && contact.Value == email {
|
||||
contact.Value = newEmail
|
||||
}
|
||||
}
|
||||
}
|
||||
return customer.Upsert()
|
||||
}
|
||||
|
||||
func (customer *Customer) ChangePassword(password, passwordNew string, force bool) error {
|
||||
err := ChangePassword(customer.Email, password, passwordNew, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return customer.Upsert()
|
||||
}
|
||||
|
||||
// Unlinks customer from database
|
||||
// After unlink, persistent changes on customer are no longer possible until it is retrieved again from db.
|
||||
func (customer *Customer) UnlinkFromDB() {
|
||||
customer.unlinkDB = true
|
||||
}
|
||||
|
||||
func (customer *Customer) LinkDB() {
|
||||
customer.unlinkDB = false
|
||||
}
|
||||
|
||||
func (customer *Customer) insert() error {
|
||||
return insertCustomer(customer)
|
||||
}
|
||||
@ -213,10 +176,6 @@ func (customer *Customer) Delete() error {
|
||||
return DeleteCustomer(customer)
|
||||
}
|
||||
|
||||
func (customer *Customer) Rollback(version int) error {
|
||||
return Rollback(customer.GetID(), version)
|
||||
}
|
||||
|
||||
func (customer *Customer) OverrideId(id string) error {
|
||||
customer.Id = id
|
||||
return customer.Upsert()
|
||||
@ -350,43 +309,10 @@ func (customer *Customer) ChangeAddress(addr *address.Address) error {
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// ~ PUBLIC METHODS
|
||||
// ~ PRIVATE METHODS
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// DiffTwoLatestCustomerVersions compares the two latest Versions of Customer found in version.
|
||||
// If openInBrowser, the result is automatically displayed in the default browser.
|
||||
func DiffTwoLatestCustomerVersions(customerId string, customProvider CustomerCustomProvider, openInBrowser bool) (string, error) {
|
||||
version, err := GetCurrentVersionOfCustomerFromVersionsHistory(customerId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return DiffCustomerVersions(customerId, version.Current-1, version.Current, customProvider, openInBrowser)
|
||||
}
|
||||
|
||||
func DiffCustomerVersions(customerId string, versionA int, versionB int, customProvider CustomerCustomProvider, openInBrowser bool) (string, error) {
|
||||
if versionA <= 0 || versionB <= 0 {
|
||||
return "", errors.New("Error: Version must be greater than 0")
|
||||
}
|
||||
name := "customer_v" + strconv.Itoa(versionA) + "_vs_v" + strconv.Itoa(versionB)
|
||||
customerVersionA, err := GetCustomerByVersion(customerId, versionA, customProvider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
customerVersionB, err := GetCustomerByVersion(customerId, versionB, customProvider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
html, err := version.DiffVersions(customerVersionA, customerVersionB)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if openInBrowser {
|
||||
err := utils.OpenInBrowser(name, html)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return html, err
|
||||
// lc returns lowercase version of string
|
||||
func lc(s string) string {
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package customer
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
@ -50,74 +49,6 @@ func createNewTestCustomer(email string) (*Customer, error) {
|
||||
return NewCustomer(addrKey, addrKeyHash, externalID, mailContact, FooCustomProvider{})
|
||||
}
|
||||
|
||||
func TestCustomerGetLatestCustomerFromDb(t *testing.T) {
|
||||
test_utils.DropAllCollections()
|
||||
customer, err := createNewTestCustomer(MOCK_EMAIL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Perform 3 Upserts
|
||||
customer.Person.FirstName = "Foo"
|
||||
err = customer.Upsert()
|
||||
customer.Person.MiddleName = "Bob"
|
||||
err = customer.Upsert()
|
||||
customer.Person.LastName = "Bar"
|
||||
err = customer.Upsert()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if version number is 3
|
||||
customer, err = GetCurrentCustomerByIdFromVersionsHistory(customer.GetID(), nil)
|
||||
if customer.GetVersion().Current != 3 {
|
||||
log.Println("Version is ", customer.GetVersion().Current, "- should have been 3.")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomerDiff2LatestCustomerVersions(t *testing.T) {
|
||||
customer1, _ := create2CustomersAndPerformSomeUpserts(t)
|
||||
|
||||
_, err := DiffTwoLatestCustomerVersions(customer1.GetID(), nil, OPEN_DIFFS_IN_BROWSER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomerRollbackAndDiff(t *testing.T) {
|
||||
customer1, _ := create2CustomersAndPerformSomeUpserts(t)
|
||||
|
||||
errRoll := customer1.Rollback(customer1.GetVersion().Current - 1)
|
||||
if errRoll != nil {
|
||||
t.Fatal(errRoll)
|
||||
}
|
||||
customer1, errRoll = GetCustomerById(customer1.GetID(), nil)
|
||||
|
||||
_, err := DiffTwoLatestCustomerVersions(customer1.GetID(), nil, OPEN_DIFFS_IN_BROWSER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomerRollback(t *testing.T) {
|
||||
customer1, _ := create2CustomersAndPerformSomeUpserts(t)
|
||||
utils.PrintJSON(customer1)
|
||||
log.Println("Version", customer1.GetVersion(), "FirstName", customer1.Person.FirstName)
|
||||
err := customer1.Rollback(customer1.GetVersion().Current - 2) // We need tp go 2 versions back to see the name change
|
||||
if err != nil {
|
||||
fmt.Println("Error: Could not roll back to previous version!")
|
||||
t.Fatal(err)
|
||||
}
|
||||
customer1, err = GetCustomerById(customer1.GetID(), nil)
|
||||
log.Println("Version", customer1.GetVersion(), "FirstName", customer1.Person.FirstName)
|
||||
|
||||
// Due to Rollback, FirstName should be "Foo" again
|
||||
if customer1.Person.FirstName != "Foo" {
|
||||
fmt.Println("Error: Expected Name to be Foo but got " + customer1.Person.FirstName)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func create2CustomersAndPerformSomeUpserts(t *testing.T) (*Customer, *Customer) {
|
||||
test_utils.DropAllCollections()
|
||||
customer, err := createNewTestCustomer(MOCK_EMAIL)
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
stderr "errors"
|
||||
|
||||
"github.com/foomo/shop/configuration"
|
||||
"github.com/foomo/shop/persistence"
|
||||
"github.com/foomo/shop/shop_error"
|
||||
"github.com/foomo/shop/version"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
@ -21,9 +18,8 @@ import (
|
||||
//------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
globalCustomerPersistor *persistence.Persistor
|
||||
globalCustomerVersionsPersistor *persistence.Persistor
|
||||
globalCredentialsPersistor *persistence.Persistor
|
||||
globalCustomerPersistor *persistence.Persistor
|
||||
globalCredentialsPersistor *persistence.Persistor
|
||||
|
||||
customerEnsuredIndexes = []mgo.Index{
|
||||
{
|
||||
@ -88,56 +84,6 @@ func GetCustomerPersistor() *persistence.Persistor {
|
||||
return globalCustomerPersistor
|
||||
}
|
||||
|
||||
// GetCustomerVersionsPersistor will return a singleton instance of a versioned customer mongo persistor
|
||||
func GetCustomerVersionsPersistor() *persistence.Persistor {
|
||||
url := configuration.GetMongoURL()
|
||||
collection := configuration.MONGO_COLLECTION_CUSTOMERS_HISTORY
|
||||
if globalCustomerVersionsPersistor == nil {
|
||||
p, err := persistence.NewPersistor(url, collection)
|
||||
if err != nil || p == nil {
|
||||
panic(errors.New("failed to create mongoDB order persistor: " + err.Error()))
|
||||
}
|
||||
globalCustomerVersionsPersistor = p
|
||||
return globalCustomerVersionsPersistor
|
||||
}
|
||||
|
||||
if url == globalCustomerVersionsPersistor.GetURL() && collection == globalCustomerVersionsPersistor.GetCollectionName() {
|
||||
return globalCustomerVersionsPersistor
|
||||
}
|
||||
|
||||
p, err := persistence.NewPersistor(url, collection)
|
||||
if err != nil || p == nil {
|
||||
panic(err)
|
||||
}
|
||||
globalCustomerVersionsPersistor = p
|
||||
return globalCustomerVersionsPersistor
|
||||
}
|
||||
|
||||
// GetCredentialsPersistor will return a singleton instance of a credentials mongo persistor
|
||||
func GetCredentialsPersistor() *persistence.Persistor {
|
||||
url := configuration.GetMongoURL()
|
||||
collection := configuration.MONGO_COLLECTION_CREDENTIALS
|
||||
if globalCredentialsPersistor == nil {
|
||||
p, err := persistence.NewPersistor(url, collection)
|
||||
if err != nil || p == nil {
|
||||
panic(errors.New("failed to create mongoDB order persistor: " + err.Error()))
|
||||
}
|
||||
globalCredentialsPersistor = p
|
||||
return globalCredentialsPersistor
|
||||
}
|
||||
|
||||
if url == globalCredentialsPersistor.GetURL() && collection == globalCredentialsPersistor.GetCollectionName() {
|
||||
return globalCredentialsPersistor
|
||||
}
|
||||
|
||||
p, err := persistence.NewPersistor(url, collection)
|
||||
if err != nil || p == nil {
|
||||
panic(err)
|
||||
}
|
||||
globalCredentialsPersistor = p
|
||||
return globalCredentialsPersistor
|
||||
}
|
||||
|
||||
// AlreadyExistsInDB checks if a customer with given customerID already exists in the database
|
||||
func AlreadyExistsInDB(customerID string) (bool, error) {
|
||||
session, collection := GetCustomerPersistor().GetCollection()
|
||||
@ -165,7 +111,7 @@ func Find(query *bson.M, customProvider CustomerCustomProvider) (iter func() (cu
|
||||
|
||||
_, errCount := q.Count()
|
||||
if errCount != nil {
|
||||
if errors.Is(errCount, mgo.ErrNotFound) {
|
||||
if stderr.Is(errCount, mgo.ErrNotFound) {
|
||||
return nil, ErrCustomerNotFound
|
||||
}
|
||||
return nil, errCount
|
||||
@ -185,61 +131,29 @@ func Find(query *bson.M, customProvider CustomerCustomProvider) (iter func() (cu
|
||||
// UpsertCustomer will save a given customer in mongo collection
|
||||
func UpsertCustomer(c *Customer) error {
|
||||
|
||||
// order is unlinked or not yet inserted in db
|
||||
if c.unlinkDB || c.BsonId == "" {
|
||||
return nil
|
||||
}
|
||||
session, collection := GetCustomerPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
// Get current version from db and check against verssion of c
|
||||
// If they are not identical, there must have been another upsert which would be overwritten by this one.
|
||||
// In this case upsert is skipped and an error is returned,
|
||||
customerLatestFromDb := &Customer{}
|
||||
err := collection.Find(&bson.M{"_id": c.BsonId}).Select(&bson.M{"version": 1}).One(customerLatestFromDb)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Upsert failed: Could not find customer with id", c.GetID(), "Error:", err)
|
||||
|
||||
return err
|
||||
if c.Version == nil {
|
||||
return errors.Wrap(shop_error.ErrorVersionConflict, "version must not be empty")
|
||||
}
|
||||
|
||||
latestVersionInDb := customerLatestFromDb.Version.GetVersion()
|
||||
if latestVersionInDb != c.Version.GetVersion() && !c.Flags.forceUpsert {
|
||||
errMsg := fmt.Sprintln("WARNING: Cannot upsert latest version ", strconv.Itoa(latestVersionInDb), "in db with version", strconv.Itoa(c.Version.GetVersion()), "!")
|
||||
log.Println(errMsg)
|
||||
return errors.New(errMsg)
|
||||
currentVersion := c.Version.Current
|
||||
c.Version.Increment()
|
||||
if currentVersion == 0 {
|
||||
// it is not allowed to insert customers, @see NewCustomer method instead
|
||||
return shop_error.ErrorVersionConflict
|
||||
}
|
||||
|
||||
if c.Flags.forceUpsert {
|
||||
// Remember this number, so that we later know from which version we came from
|
||||
v := c.Version.Current
|
||||
// Set the current version number to keep history consistent
|
||||
c.Version.Current = latestVersionInDb
|
||||
c.Version.Increment()
|
||||
c.Flags.forceUpsert = false
|
||||
// Overwrite NumberPrevious, to remember where we came from
|
||||
c.Version.Previous = v
|
||||
} else {
|
||||
c.Version.Increment()
|
||||
// upsert existing customer
|
||||
err := collection.Update(bson.M{
|
||||
"$and": []bson.M{
|
||||
{KeyAddrKey: c.AddrKey},
|
||||
{"version.current": currentVersion},
|
||||
}}, c)
|
||||
if stderr.Is(err, mgo.ErrNotFound) {
|
||||
return shop_error.ErrorVersionConflict
|
||||
}
|
||||
|
||||
_, err = collection.UpsertId(c.BsonId, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return saveCustomerVersionHistory(c)
|
||||
}
|
||||
|
||||
func saveCustomerVersionHistory(c *Customer) error {
|
||||
// Store version in history
|
||||
currentID := c.BsonId
|
||||
c.BsonId = "" // Temporarily reset Mongo ObjectId, so that we can perfrom an Insert.
|
||||
session, collection := GetCustomerVersionsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
err := collection.Insert(c)
|
||||
c.BsonId = currentID // restore bsonId
|
||||
return err
|
||||
}
|
||||
|
||||
@ -257,12 +171,10 @@ func DeleteCustomer(c *Customer) error {
|
||||
|
||||
// remove customer
|
||||
err := collection.Remove(bson.M{"_id": c.BsonId})
|
||||
if err != nil && err.Error() != shop_error.ErrorNotInDatabase {
|
||||
return err
|
||||
if stderr.Is(mgo.ErrNotFound, err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove credentials
|
||||
return DeleteCredential(c.Email)
|
||||
return err
|
||||
}
|
||||
func DeleteCustomerById(id string) error {
|
||||
customer, err := GetCustomerById(id, nil)
|
||||
@ -282,45 +194,12 @@ func GetCustomerByAddrKeyHash(addrKeyHash string, customProvider CustomerCustomP
|
||||
}
|
||||
|
||||
func GetCustomerByQuery(query *bson.M, customProvider CustomerCustomProvider) (*Customer, error) {
|
||||
return findOneCustomer(query, nil, "", customProvider, false)
|
||||
return findOneCustomer(query, nil, "", customProvider)
|
||||
}
|
||||
|
||||
// GetCustomerById returns the customer with id
|
||||
func GetCustomerById(id string, customProvider CustomerCustomProvider) (*Customer, error) {
|
||||
return findOneCustomer(&bson.M{"id": id}, nil, "", customProvider, false)
|
||||
}
|
||||
|
||||
func GetCurrentCustomerByIdFromVersionsHistory(customerId string, customProvider CustomerCustomProvider) (*Customer, error) {
|
||||
return findOneCustomer(&bson.M{"id": customerId}, nil, "-version.current", customProvider, true)
|
||||
}
|
||||
func GetCurrentVersionOfCustomerFromVersionsHistory(customerId string) (*version.Version, error) {
|
||||
customer, err := findOneCustomer(&bson.M{"id": customerId}, &bson.M{"version": 1}, "-version.current", nil, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return customer.GetVersion(), nil
|
||||
}
|
||||
func GetCustomerByVersion(customerId string, version int, customProvider CustomerCustomProvider) (*Customer, error) {
|
||||
return findOneCustomer(&bson.M{"id": customerId, "version.current": version}, nil, "", customProvider, true)
|
||||
}
|
||||
|
||||
func Rollback(customerId string, version int) error {
|
||||
currentCustomer, err := GetCustomerById(customerId, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version >= currentCustomer.GetVersion().Current || version < 0 {
|
||||
return errors.New("Cannot perform rollback to " + strconv.Itoa(version) + " from version " + strconv.Itoa(currentCustomer.GetVersion().Current))
|
||||
}
|
||||
customerFromVersionsHistory, err := GetCustomerByVersion(customerId, version, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set bsonId from current customer to customer from history to overwrite current customer on next upsert.
|
||||
customerFromVersionsHistory.BsonId = currentCustomer.BsonId
|
||||
customerFromVersionsHistory.Flags.forceUpsert = true
|
||||
return customerFromVersionsHistory.Upsert()
|
||||
|
||||
return findOneCustomer(&bson.M{"id": id}, nil, "", customProvider)
|
||||
}
|
||||
|
||||
func DropAllCustomers() error {
|
||||
@ -330,34 +209,15 @@ func DropAllCustomers() error {
|
||||
return collection.DropCollection()
|
||||
|
||||
}
|
||||
func DropAllCredentials() error {
|
||||
session, collection := GetCredentialsPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
return collection.DropCollection()
|
||||
}
|
||||
|
||||
func DropAllCustomersAndCredentials() error {
|
||||
if err := DropAllCustomers(); err != nil {
|
||||
return err
|
||||
}
|
||||
return DropAllCredentials()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// ~ PRIVATE METHODS
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// findOneCustomer returns one Customer from the customer database or from the customer history database
|
||||
func findOneCustomer(find *bson.M, selection *bson.M, sort string, customProvider CustomerCustomProvider, fromHistory bool) (*Customer, error) {
|
||||
var session *mgo.Session
|
||||
var collection *mgo.Collection
|
||||
func findOneCustomer(find *bson.M, selection *bson.M, sort string, customProvider CustomerCustomProvider) (*Customer, error) {
|
||||
|
||||
if fromHistory {
|
||||
session, collection = GetCustomerVersionsPersistor().GetCollection()
|
||||
} else {
|
||||
session, collection = GetCustomerPersistor().GetCollection()
|
||||
}
|
||||
session, collection := GetCustomerPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
|
||||
customer := &Customer{}
|
||||
@ -370,7 +230,7 @@ func findOneCustomer(find *bson.M, selection *bson.M, sort string, customProvide
|
||||
if sort != "" {
|
||||
err := collection.Find(find).Select(selection).Sort(sort).One(customer)
|
||||
if err != nil {
|
||||
if errors.Is(err, mgo.ErrNotFound) {
|
||||
if stderr.Is(err, mgo.ErrNotFound) {
|
||||
return nil, ErrCustomerNotFound
|
||||
}
|
||||
return nil, err
|
||||
@ -378,7 +238,7 @@ func findOneCustomer(find *bson.M, selection *bson.M, sort string, customProvide
|
||||
} else {
|
||||
err := collection.Find(find).Select(selection).One(customer)
|
||||
if err != nil {
|
||||
if errors.Is(err, mgo.ErrNotFound) {
|
||||
if stderr.Is(err, mgo.ErrNotFound) {
|
||||
return nil, ErrCustomerNotFound
|
||||
}
|
||||
return nil, err
|
||||
@ -402,22 +262,10 @@ func findOneCustomer(find *bson.M, selection *bson.M, sort string, customProvide
|
||||
func insertCustomer(c *Customer) error {
|
||||
session, collection := GetCustomerPersistor().GetCollection()
|
||||
defer session.Close()
|
||||
alreadyExists, err := AlreadyExistsInDB(c.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
err := collection.Insert(c)
|
||||
if mgo.IsDup(err) {
|
||||
return errors.Wrap(shop_error.ErrorDuplicateKey, err.Error())
|
||||
}
|
||||
if alreadyExists {
|
||||
log.Println("User with id", c.GetID(), "already exists in the database!")
|
||||
return nil
|
||||
}
|
||||
err = collection.Insert(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hsession, hcollection := GetCustomerVersionsPersistor().GetCollection()
|
||||
defer hsession.Close()
|
||||
err = hcollection.Insert(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@ -10,6 +10,7 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.3.0
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
|
||||
1
go.sum
1
go.sum
@ -64,6 +64,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96d
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
package shop_error
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorVersionConflict version conflict while upserting to mongo
|
||||
var ErrorVersionConflict = errors.New("version conflict")
|
||||
|
||||
// ErrorDuplicateKey an index key constraint mismatch
|
||||
var ErrorDuplicateKey = errors.New("duplicate key")
|
||||
|
||||
const (
|
||||
ErrorAlreadyExists = "already existing in db" // do not change, this string is returned by MongoDB
|
||||
|
||||
Loading…
Reference in New Issue
Block a user