mirror of
https://github.com/foomo/shop.git
synced 2025-10-16 12:35:39 +00:00
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:
commit
78a63271f1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
!.*git
|
||||
diff-*
|
||||
/vendor
|
||||
@ -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
6
.vscode/settings.json
vendored
Normal 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",
|
||||
}
|
||||
}
|
||||
5
Makefile
5
Makefile
@ -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
|
||||
@ -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!"
|
||||
}
|
||||
|
||||
112
address/address_person_migration.go
Normal file
112
address/address_person_migration.go
Normal 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
|
||||
}
|
||||
109
address/address_person_migration_test.go
Normal file
109
address/address_person_migration_test.go
Normal 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
42
address/contact.go
Normal 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
53
address/mail.go
Normal 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
70
address/phone.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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 == "" {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
53
glide.lock
generated
Normal 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
32
glide.yaml
Normal 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
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user