Merge pull request #3 from foomo/feature/contact-migration

merge back feature/contact-migration

- breaking change: email is no longer unique (due to guest accounts)
- breaking change: modified data structure for contacts
- data migration (on-the-fly) for new contact data structure
This commit is contained in:
florianschlegel 2018-05-29 12:02:57 +02:00 committed by Frederik Löffert
commit 78a63271f1
20 changed files with 619 additions and 107 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
!.*git
diff-*
/vendor

View File

@ -5,5 +5,10 @@ sudo: required
services:
- docker
install:
- go get -v github.com/Masterminds/glide
- cd $GOPATH/src/github.com/Masterminds/glide && git checkout v0.13.1 && go install && cd -
- glide install
script:
- make install-test-dependencies test
- make test

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
// Place your settings in this file to overwrite default and user settings.
{
"go.testEnvVars": {
"MONGO_URL": "mongodb://127.0.0.1:27017/shop",
}
}

View File

@ -3,6 +3,9 @@ SHELL = "/bin/bash"
TEST_PATH = github.com/foomo/shop
# invoke a single test by setting go test -v $(TEST_PATH)/shop
mongo:
docker run --rm -d -it -p 27017:27017 mongo
clean:
rm -f customer/diff-*
@ -14,6 +17,6 @@ install-test-dependencies:
go get -u github.com/bwmarrin/snowflake
go get -u github.com/sergi/go-diff/diffmatchpatch
go get -u github.com/nbutton23/zxcvbn-go
go get -u gopkg.in/mgo.v2/bson
go get -u github.com/mitchellh/mapstructure
go get -u gopkg.in/mgo.v2/bson
go get -u golang.org/x/crypto/bcrypt

View File

@ -6,28 +6,36 @@ type SalutationType string
type TitleType string
const (
AddressDefaultBilling AddressType = "addresDefaultBilling"
AddressDefaultShipping AddressType = "addressDefaultShipping"
AddressOther AddressType = "addressOther"
ContactTypePhoneLandline ContactType = "landline"
ContactTypePhoneMobile ContactType = "mobile"
ContactTypeEmail ContactType = "email"
ContactTypeSkype ContactType = "skype"
ContactTypeFax ContactType = "fax"
SalutationTypeMr SalutationType = "male" //"Mr"
SalutationTypeMrs SalutationType = "female" //"Mrs"
SalutationTypeMrAndMrs SalutationType = "MrAndMrs"
SalutationTypeCompany SalutationType = "Company" // TODO: find better wording
SalutationTypeFamily SalutationType = "Family" // TODO: find better wording
TitleTypeNone TitleType = ""
TitleTypeDr TitleType = "Dr"
TitleTypeProf TitleType = "Prof."
TitleTypeProfDr TitleType = "Prof. Dr."
TitleTypePriest TitleType = "Priest" // TODO: find better wording
AddressDefaultBilling AddressType = "addresDefaultBilling"
AddressDefaultShipping AddressType = "addressDefaultShipping"
AddressOther AddressType = "addressOther"
ContactTypeEmail ContactType = "email"
ContactTypePhone ContactType = "phone"
ContactTypePhoneMobile ContactType = "mobile"
ContactTypePhoneLandline ContactType = "landline"
ContactTypeFax ContactType = "fax"
ContactTypeSkype ContactType = "skype"
ContactTypeSlack ContactType = "slack"
ContactTypeTwitter ContactType = "twitter"
ContactTypeFacebook ContactType = "facebook"
SalutationTypeMr SalutationType = "male" //"Mr"
SalutationTypeMrs SalutationType = "female" //"Mrs"
SalutationTypeMrAndMrs SalutationType = "MrAndMrs"
SalutationTypeCompany SalutationType = "Company" // TODO: find better wording
SalutationTypeFamily SalutationType = "Family" // TODO: find better wording
TitleTypeNone TitleType = ""
TitleTypeDr TitleType = "Dr"
TitleTypeProf TitleType = "Prof."
TitleTypeProfDr TitleType = "Prof. Dr."
TitleTypePriest TitleType = "Priest" // TODO: find better wording
)
type Address struct {
Id string // is automatically set on AddAddress()
ExternalID string
Person *Person
Type AddressType
Street string
@ -47,20 +55,14 @@ type Address struct {
// Person is a field Customer and of Address
// Only Customer->Person has Contacts
type Person struct {
FirstName string
MiddleName string
LastName string
Title TitleType
Salutation SalutationType
Birthday string
Contacts *Contacts
}
type Contacts struct {
PhoneLandLine string
PhoneMobile string
Email string
Skype string
Primary ContactType
FirstName string
MiddleName string
LastName string
Title TitleType
Salutation SalutationType
Birthday string
Contacts map[string]*Contact // key must be contactID
DefaultContacts map[ContactType]string // reference by contactID
}
func (address *Address) GetID() string {
@ -95,20 +97,3 @@ func (address *Address) Equals(otherAddress *Address) bool {
return equal
}
// GetPrimaryContact returns primary contact as string
func (c *Contacts) GetPrimaryContact() string {
switch c.Primary {
case ContactTypePhoneLandline:
return string(ContactTypePhoneLandline) + ": " + c.PhoneLandLine
case ContactTypePhoneMobile:
return string(ContactTypePhoneMobile) + ": " + c.PhoneMobile
case ContactTypeEmail:
return string(ContactTypeEmail) + ": " + c.Email
case ContactTypeSkype:
return string(ContactTypeSkype) + ": " + c.Skype
// case ContactTypeFax: // 2016 anyone??
// return string(ContactTypeFax) + ": " + c.Fax
}
return "No primary contact available!"
}

View File

@ -0,0 +1,112 @@
package address
import (
"github.com/foomo/shop/unique"
"gopkg.in/mgo.v2/bson"
)
//ContactsDeprecated is an old structure how contact data was stored in the past => it's deprecated
type ContactsDeprecated struct {
PhoneLandLine string
PhoneMobile string
Email string
Skype string
Primary ContactType
}
// SetBSON to unmarshal BSON in a Person struct while migrating old objects into new person struct
func (p *Person) SetBSON(raw bson.Raw) error {
return bsonDecodeNewPersonStruct(p, raw)
}
func bsonDecodeOldPersonStruct(p *Person, raw bson.Raw) error {
// expected struct (old)
decodedOld := new(struct {
FirstName string
MiddleName string
LastName string
Title TitleType
Salutation SalutationType
Birthday string
Contacts *ContactsDeprecated
})
// unmarshal
bsonErr := raw.Unmarshal(decodedOld)
// error
if bsonErr != nil {
return bsonErr
}
// map values
p.FirstName = decodedOld.FirstName
p.MiddleName = decodedOld.MiddleName
p.LastName = decodedOld.LastName
p.Title = decodedOld.Title
p.Salutation = decodedOld.Salutation
p.Birthday = decodedOld.Birthday
p.Contacts = map[string]*Contact{}
p.DefaultContacts = map[ContactType]string{}
appendContact := func(p *Person, contactValue string, contactType ContactType) {
if contactValue != "" {
id := unique.GetNewIDShortID()
contact := &Contact{
ID: id,
Type: contactType,
Value: contactValue,
}
p.Contacts[contact.ID] = contact
p.DefaultContacts[contactType] = contact.ID
}
}
appendContact(p, decodedOld.Contacts.Email, ContactTypeEmail)
appendContact(p, decodedOld.Contacts.PhoneLandLine, ContactTypePhoneLandline)
appendContact(p, decodedOld.Contacts.PhoneMobile, ContactTypePhoneMobile)
appendContact(p, decodedOld.Contacts.Skype, ContactTypeSkype)
// no error
return nil
}
func bsonDecodeNewPersonStruct(p *Person, raw bson.Raw) error {
// expected struct
decoded := new(struct {
FirstName string
MiddleName string
LastName string
Title TitleType
Salutation SalutationType
Birthday string
Contacts map[string]*Contact
DefaultContacts map[ContactType]string
})
// unmarshall
bsonErr := raw.Unmarshal(decoded)
// error
if bsonErr != nil {
return bsonErr
}
// no contacts decoded, try to decode old struct instead
if decoded.Contacts == nil {
return bsonDecodeOldPersonStruct(p, raw)
}
// map values
p.FirstName = decoded.FirstName
p.MiddleName = decoded.MiddleName
p.LastName = decoded.LastName
p.Title = decoded.Title
p.Salutation = decoded.Salutation
p.Birthday = decoded.Birthday
p.Contacts = decoded.Contacts
p.DefaultContacts = decoded.DefaultContacts
// no error
return nil
}

View File

@ -0,0 +1,109 @@
package address
import (
"testing"
"github.com/foomo/shop/unique"
"github.com/stretchr/testify/assert"
"gopkg.in/mgo.v2/bson"
)
func TestPersonContactsMigration(t *testing.T) {
// marshall empty person
var emptyPerson Person
emptyPersonMarshal, emptyPersonMarshalErr := bson.Marshal(emptyPerson)
assert.NoError(t, emptyPersonMarshalErr, "marshal emptyPerson failed")
emptyPersonUnmarshal := &Person{}
emptyPersonUnmarshalErr := bson.Unmarshal(emptyPersonMarshal, emptyPersonUnmarshal)
assert.NoError(t, emptyPersonUnmarshalErr, "unmarshal emptyPerson failed")
// marshall sepp
sepp := &Person{}
sepp.FirstName = "Sepp"
seppMarshal, seppMarshalErr := bson.Marshal(sepp)
assert.NoError(t, seppMarshalErr, "marshal sepp failed")
seppUnmarshal := &Person{}
seppUnmarshalErr := bson.Unmarshal(seppMarshal, seppUnmarshal)
assert.NoError(t, seppUnmarshalErr, "unmarshal sepp failed")
// marshall max
p := &Person{
Salutation: SalutationTypeMr,
Title: TitleTypeDr,
FirstName: "Max",
LastName: "Mustermann",
Birthday: "1973-12-22",
Contacts: []*Contact{
&Contact{
ID: unique.GetNewIDShortID(),
IsDefault: true,
Type: ContactTypeEmail,
Value: "foo@example.com",
},
&Contact{
ID: unique.GetNewIDShortID(),
IsDefault: true,
Type: ContactTypePhone,
Value: "+49 1234 56 78 990",
},
},
}
m, marshalErr := bson.Marshal(p)
assert.NoError(t, marshalErr, "marshal person failed")
u := &Person{}
unmarshalErr := bson.Unmarshal(m, u)
assert.NoError(t, unmarshalErr, "unmarshal person failed")
assert.Equal(t, p.Salutation, u.Salutation)
assert.Equal(t, p.Title, u.Title)
assert.Equal(t, p.FirstName, u.FirstName)
assert.Equal(t, p.LastName, u.LastName)
assert.Equal(t, p.Birthday, u.Birthday)
assert.Len(t, p.Contacts, 2, "expected two contact types")
assert.Len(t, u.Contacts, 2, "expected two contact types")
type OldPersonStruct struct {
FirstName string
MiddleName string
LastName string
Title TitleType
Salutation SalutationType
Birthday string
Contacts *ContactsDeprecated
}
old := &OldPersonStruct{
Salutation: p.Salutation,
Title: p.Title,
FirstName: p.FirstName,
LastName: p.LastName,
Birthday: p.Birthday,
Contacts: &ContactsDeprecated{
Email: "foo@example.com",
PhoneMobile: "+49 1234 56 78 990",
},
}
m, marshalErr = bson.Marshal(old)
assert.NoError(t, marshalErr, "marshal person failed")
u = &Person{}
unmarshalErr = bson.Unmarshal(m, u)
assert.NoError(t, unmarshalErr, "unmarshal person failed")
assert.NotNil(t, u.Contacts, "contacts must not be empty")
assert.Len(t, u.Contacts, 2, "expected two contact types")
}

42
address/contact.go Normal file
View File

@ -0,0 +1,42 @@
package address
type Contact struct {
ID string
ExternalID string
Type ContactType
Value string
}
// GetContactByType will search for given contact type
// if possible the default entry for that type will be returned
// if no default exists at least any of that type will be returned
// if none of that type exists it will be nil
func (p *Person) GetContactByType(contactType ContactType) *Contact {
// try to load default first
defaultContact := p.GetDefaultContactByType(contactType)
if defaultContact != nil {
return defaultContact
}
// no default ... try to find a fallback of same type
for _, c := range p.Contacts {
if c.Type == contactType {
return c
}
}
// we failed ... contact type is not present
return nil
}
func (p *Person) GetDefaultContactByType(contactType ContactType) *Contact {
// try to return default
if id, ok := p.DefaultContacts[contactType]; ok {
if c, ok := p.Contacts[id]; ok {
return c
}
}
return nil
}

53
address/mail.go Normal file
View File

@ -0,0 +1,53 @@
package address
import (
"strings"
"github.com/foomo/shop/unique"
)
// IsMail if type is ContactTypeEmail
func (c *Contact) IsMail() bool {
return c.Type == ContactTypeEmail
}
// CreateMailContact will return a mail contact for given mail address, a unique ID is generated automatically
func CreateMailContact(mail string) *Contact {
return &Contact{
ID: unique.GetNewIDShortID(),
Type: ContactTypeEmail,
Value: strings.ToLower(mail),
}
}
// SetMail will set or replace the default mail. If no default mail is present it will replace first mail contact. Otherwise a new default mail contact is created.
func (p *Person) SetMail(mail string) *Contact {
contact := p.GetContactByType(ContactTypeEmail)
if contact == nil {
if p.Contacts == nil {
p.Contacts = map[string]*Contact{}
}
if p.DefaultContacts == nil {
p.DefaultContacts = map[ContactType]string{}
}
contact = CreateMailContact(mail)
p.Contacts[contact.ID] = contact
p.DefaultContacts[ContactTypeEmail] = contact.ID
}
contact.Value = strings.ToLower(mail)
return contact
}
// GetMailAddress will return the email address string or an empty string if not yet set
func (p *Person) GetMailAddress() string {
mail := p.GetMailContact()
if mail != nil {
return mail.Value
}
return ""
}
// GetMailContact will return the default contact object for contactType mail or nil of not yet set
func (p *Person) GetMailContact() *Contact {
return p.GetContactByType(ContactTypeEmail)
}

70
address/phone.go Normal file
View File

@ -0,0 +1,70 @@
package address
import "github.com/foomo/shop/unique"
// IsPhone if type is in (ContactTypePhone, ContactTypePhoneMobile, ContactTypePhoneLandline)
func (c *Contact) IsPhone() bool {
return c.Type == ContactTypePhone || c.Type == ContactTypePhoneMobile || c.Type == ContactTypePhoneLandline
}
// CreatePhoneContact will return a phone contact for given phone number, a unique ID is generated automatically
func CreatePhoneContact(phone string) *Contact {
return &Contact{
ID: unique.GetNewIDShortID(),
Type: ContactTypePhone,
Value: phone,
}
}
// SetPhone will set or replace the default phone. If no default phone is present it will replace first phone contact. Otherwise a new default phone contact is created.
func (p *Person) SetPhone(phone string) *Contact {
contact := p.GetContactByType(ContactTypePhone)
if contact == nil {
if p.Contacts == nil {
p.Contacts = map[string]*Contact{}
}
if p.DefaultContacts == nil {
p.DefaultContacts = map[ContactType]string{}
}
contact = CreatePhoneContact(phone)
p.Contacts[contact.ID] = contact
p.DefaultContacts[ContactTypePhone] = contact.ID
}
contact.Value = phone
return contact
}
// GetPhoneNumber will return the default phone number or an empty string if not yet set
func (p *Person) GetPhoneNumber() string {
phone := p.GetPhoneContact()
if phone != nil {
return phone.Value
}
return ""
}
// GetPhoneContact returns a phone contact or nil if none is stored yet
// tries to find types in the given order: ContactTypePhone first, ContactTypePhoneMobile second, ContactTypePhoneLandline last
// if one of these contact types is marked as default it will be returned immediately
func (p *Person) GetPhoneContact() *Contact {
// try to load default contact first for phone or mobile or landline (in that exact order)
types := []ContactType{ContactTypePhone, ContactTypePhoneMobile, ContactTypePhoneLandline}
for _, contactType := range types {
defaultContact := p.GetDefaultContactByType(contactType)
if defaultContact != nil {
return defaultContact
}
}
// try to load any phone contact in given order: phone, mobile, landline
for _, contactType := range types {
contact := p.GetContactByType(contactType)
if contact != nil {
return contact
}
}
// no phone contact
return nil
}

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/foomo/shop/crypto"
"github.com/foomo/shop/shop_error"
"github.com/foomo/shop/version"
"gopkg.in/mgo.v2/bson"
)
@ -65,12 +66,17 @@ func CreateCustomerCredentials(email, password string) error {
}
// CheckLoginAvailable returns true if the email address is available as login credential
// 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{"email": lc(email)})
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
@ -146,7 +152,13 @@ func DeleteCredential(email string) error {
session, collection := GetCustomerPersistor().GetCollection()
defer session.Close()
return collection.Remove(&bson.M{"email": lc(email)})
// remove credentials
err := collection.Remove(&bson.M{"email": lc(email)})
if err != nil && err.Error() != shop_error.ErrorNotInDatabase {
return err
}
return nil
}
//------------------------------------------------------------------

View File

@ -24,14 +24,19 @@ func TestCredentials(t *testing.T) {
if err != nil {
t.Fatal(err)
}
// 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)
}
// ! ----------------------------------------------------------------------
// ~ 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")

View File

@ -44,7 +44,8 @@ type CountryCode string
type Customer struct {
BsonId bson.ObjectId `bson:"_id,omitempty"`
Id string // Email is used as LoginID, but can change. This is never changes!
unlinkDB bool // if true, changes to Customer are not stored in database
ExternalID string
unlinkDB bool // if true, changes to Customer are not stored in database
Flags *Flags
Version *version.Version
CreatedAt time.Time
@ -99,7 +100,7 @@ func NewGuestCustomer(email string, customProvider CustomerCustomProvider) (*Cus
// NewCustomer creates a new Customer in the database and returns it.
// Email must be unique for a customer. customerProvider may be nil at this point.
func NewCustomer(email, password string, customProvider CustomerCustomProvider) (*Customer, error) {
log.Println("=== Creating new customer ", email)
//log.Println("=== Creating new customer ", email)
if email == "" {
return nil, errors.New(shop_error.ErrorRequiredFieldMissing)
}
@ -123,6 +124,7 @@ func NewCustomer(email, password string, customProvider CustomerCustomProvider)
// // }
// }
mailContact := address.CreateMailContact(email)
customer := &Customer{
Flags: &Flags{},
Version: version.NewVersion(),
@ -131,19 +133,22 @@ func NewCustomer(email, password string, customProvider CustomerCustomProvider)
CreatedAt: utils.TimeNow(),
LastModifiedAt: utils.TimeNow(),
Person: &address.Person{
Contacts: &address.Contacts{
Email: email,
Contacts: map[string]*address.Contact{
mailContact.ID: mailContact,
},
DefaultContacts: map[address.ContactType]string{
address.ContactTypeEmail: mailContact.ID,
},
},
Localization: &Localization{},
Tracking: &Tracking{},
}
trackingId, err := crypto.CreateHash(customer.GetID())
trackingID, err := crypto.CreateHash(customer.GetID())
if err != nil {
return nil, err
}
customer.Tracking.TrackingID = "tid" + trackingId
customer.Tracking.TrackingID = "tid" + trackingID
customer.IsGuest = false
if customProvider != nil {
customer.Custom = customProvider.NewCustomerCustom()
@ -163,18 +168,21 @@ func NewCustomer(email, password string, customProvider CustomerCustomProvider)
//------------------------------------------------------------------
func (customer *Customer) ChangeEmail(email, newEmail string) error {
// err := ChangeEmail(email, newEmail)
// if err != nil {
// return err
// }
customer.Email = lc(newEmail)
// lower case
email = lc(email)
newEmail = lc(newEmail)
customer.Email = newEmail
for _, addr := range customer.GetAddresses() {
if addr.Person.Contacts.Email == lc(email) {
addr.Person.Contacts.Email = lc(newEmail)
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 {
@ -245,21 +253,22 @@ func (customer *Customer) AddAddress(addr *address.Address) (string, error) {
addr.Id = unique.GetNewID()
// Prevent nil pointer in case we get an incomplete address
if addr.Person == nil {
addr.Person = &address.Person{
Contacts: &address.Contacts{},
}
} else if addr.Person.Contacts == nil {
addr.Person.Contacts = &address.Contacts{}
addr.Person = &address.Person{}
}
if addr.Person.Contacts == nil {
addr.Person.Contacts = map[string]*address.Contact{}
addr.Person.DefaultContacts = map[address.ContactType]string{}
}
// If Person of Customer is still empty and this is the first address
// added to the customer, Person of Address is adopted for Customer
log.Println("is customer nil: ", customer == nil)
log.Println("is customer.person nil: ", customer.Person == nil)
// log.Println("is customer nil: ", customer == nil)
// log.Println("is customer.person nil: ", customer.Person == nil)
if customer.Person == nil {
log.Println("WARNING: customer.Person must not be nil: customerID: " + customer.GetID() + ", AddressID: " + addr.Id)
customer.Person = &address.Person{
Contacts: &address.Contacts{},
Contacts: map[string]*address.Contact{},
DefaultContacts: map[address.ContactType]string{},
}
*customer.Person = *addr.Person
} else if len(customer.Addresses) == 0 && customer.Person != nil && customer.Person.LastName == "" {

View File

@ -6,7 +6,6 @@ import (
"testing"
"github.com/foomo/shop/address"
"github.com/foomo/shop/shop_error"
"github.com/foomo/shop/test_utils"
"github.com/foomo/shop/utils"
)
@ -144,16 +143,6 @@ func create2CustomersAndPerformSomeUpserts(t *testing.T) (*Customer, *Customer)
return customer, customer2
}
// Try to get a customer which is not in the db
func TestCustomerTryRetrieveNonExistent(t *testing.T) {
test_utils.DropAllCollections()
_, err := GetCustomerByEmail("meNot@existent.com", nil)
if !shop_error.IsError(err, shop_error.ErrorNotInDatabase) {
t.Fail()
}
log.Println(err)
}
func TestCustomerCreateGuest(t *testing.T) {
test_utils.DropAllCollections()
guest, err := NewGuestCustomer(MOCK_EMAIL, nil)
@ -165,6 +154,33 @@ func TestCustomerCreateGuest(t *testing.T) {
}
}
func TestCustomerDelete(t *testing.T) {
test_utils.DropAllCollections()
guest, err := NewGuestCustomer(MOCK_EMAIL, nil)
if err != nil {
t.Fatal(err)
}
if guest.IsGuest {
t.Fatal("Expected isGuest to be false, but is true")
}
// test user
testCustomer, testCustomerErr := GetCustomerById(guest.Id, nil)
if testCustomerErr != nil {
t.Fatal(testCustomerErr)
}
if testCustomer.BsonId != guest.BsonId {
t.Fatal("customer missmatch")
}
// delete guest
delErr := guest.Delete()
if delErr != nil {
t.Fatal(delErr)
}
}
func TestCustomerChangeAddress(t *testing.T) {
test_utils.DropAllCollections()
customer, err := NewCustomer(MOCK_EMAIL, MOCK_PASSWORD, nil)

View File

@ -6,13 +6,15 @@ import (
"log"
"strconv"
"github.com/foomo/shop/shop_error"
"github.com/foomo/shop/configuration"
"github.com/foomo/shop/persistence"
"github.com/foomo/shop/utils"
"github.com/foomo/shop/version"
"github.com/mitchellh/mapstructure"
"gopkg.in/mgo.v2/bson"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// !! NOTE: customer must not import order !!
@ -220,13 +222,14 @@ func DeleteCustomer(c *Customer) error {
session, collection := GetCustomerPersistor().GetCollection()
defer session.Close()
// remove customer
err := collection.Remove(bson.M{"_id": c.BsonId})
if err != nil {
if err != nil && err.Error() != shop_error.ErrorNotInDatabase {
return err
}
err = DeleteCredential(c.Email)
return err
// remove credentials
return DeleteCredential(c.Email)
}
func DeleteCustomerById(id string) error {
customer, err := GetCustomerById(id, nil)
@ -246,10 +249,6 @@ func GetCustomerById(id string, customProvider CustomerCustomProvider) (*Custome
return findOneCustomer(&bson.M{"id": id}, nil, "", customProvider, false)
}
// GetCustomerByEmail // TODO this won't work for guests, because for guests there could be multiple entries for the same email address
func GetCustomerByEmail(email string, customProvider CustomerCustomProvider) (*Customer, error) {
return findOneCustomer(&bson.M{"email": email}, nil, "", customProvider, false)
}
func GetCurrentCustomerByIdFromVersionsHistory(customerId string, customProvider CustomerCustomProvider) (*Customer, error) {
return findOneCustomer(&bson.M{"id": customerId}, nil, "-version.current", customProvider, true)
}

53
glide.lock generated Normal file
View File

@ -0,0 +1,53 @@
hash: a0f08cf80d876deff8376f2c3dfee8b5d2df8117d06e5337b508329ac10dcef2
updated: 2018-03-27T21:03:11.417994706+02:00
imports:
- name: github.com/bwmarrin/snowflake
version: 3107b1dd8c5258a02e5f63347dc1c30288c75af5
- name: github.com/mitchellh/mapstructure
version: f3009df150dadf309fdee4a54ed65c124afad715
- name: github.com/nbutton23/zxcvbn-go
version: eafdab6b0663b4b528c35975c8b0e78be6e25261
subpackages:
- adjacency
- data
- entropy
- frequency
- match
- matching
- scoring
- utils/math
- name: github.com/sergi/go-diff
version: 1744e2970ca51c86172c8190fadad617561ed6e7
subpackages:
- diffmatchpatch
- name: github.com/skratchdot/open-golang
version: 75fb7ed4208cf72d323d7d02fd1a5964a7a9073c
subpackages:
- open
- name: github.com/ventu-io/go-shortid
version: 6c56cef5189ca1b3d5ef01dc07f4d611dfc0bb33
- name: golang.org/x/crypto
version: c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6
subpackages:
- bcrypt
- blowfish
- name: gopkg.in/mgo.v2
version: 3f83fa5005286a7fe593b055f0d7771a7dce4655
subpackages:
- bson
- internal/json
- internal/sasl
- internal/scram
testImports:
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
subpackages:
- assert

32
glide.yaml Normal file
View File

@ -0,0 +1,32 @@
package: github.com/foomo/shop
import:
- package: github.com/bwmarrin/snowflake
- package: github.com/mitchellh/mapstructure
- package: github.com/nbutton23/zxcvbn-go
version: ^0.1.0
subpackages:
- scoring
- package: github.com/sergi/go-diff
version: ^1.0.0
subpackages:
- diffmatchpatch
- package: github.com/skratchdot/open-golang
subpackages:
- open
- package: github.com/ventu-io/go-shortid
version: ^1.0.0
- package: golang.org/x/crypto
subpackages:
- bcrypt
- package: gopkg.in/mgo.v2
subpackages:
- bson
testImport:
- package: github.com/davecgh/go-spew
version: ^1.1.0
subpackages:
- spew
- package: github.com/stretchr/testify
version: ^1.2.1
subpackages:
- assert

View File

@ -67,6 +67,7 @@ type Order struct {
type CustomerData struct {
CustomerId string
GuestCustomerID string
CustomerType string // Private / Staff etc.
Email string
BillingAddress *address.Address
@ -335,8 +336,7 @@ func (order *Order) SetPositionQuantity(itemID string, quantity float64, crossPr
} else {
pos.Quantity = quantity
}
// use project-globus-services-1
// db.orders.find({}, {positions:1}).pretty()
return order.Upsert()
}
func (order *Order) GetPositionByItemId(itemID string) *Position {

View File

@ -2,10 +2,10 @@
TEST_PATH=github.com/foomo/shop
CONTAINER=$(docker run --rm -d -it -P mongo)
MONGO_PORT=$(docker inspect ${CONTAINER} | grep HostPort | sed 's/.*\"\([0-9]*\)".*/\1/g')
MONGO_PORT=$(docker inspect --format '{{ (index (index .NetworkSettings.Ports "27017/tcp") 0).HostPort }}' ${CONTAINER})
export MONGO_URL="mongodb://127.0.0.1:${MONGO_PORT}/shop"
export MONGO_URL_PRODUCTS="mongodb://127.0.0.1:${MONGO_PORT}/products"
#export MONGO_URL_PRODUCTS="mongodb://127.0.0.1:${MONGO_PORT}/products"
ERRORS=""
RES=0

View File

@ -38,7 +38,7 @@ func GetNewIDSnowFlake() string {
func GetNewIDShortID() string {
var seed uint64 = 3214
if generator == nil {
newGenerator, err := shortid.New(1, shortid.DefaultABC, seed)
newGenerator, err := shortid.New(1, shortid.DEFAULT_ABC, seed)
generator = newGenerator
if err != nil {
// The Shop can no longer work without this, therfore panic.