Skip to content

Commit

Permalink
CreateContact also should do lookup before trying to create new conta…
Browse files Browse the repository at this point in the history
…ct with URNs
  • Loading branch information
rowanseymour committed Sep 29, 2020
1 parent b37f98b commit daed23a
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 12 deletions.
2 changes: 1 addition & 1 deletion hooks/session_triggered.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (h *InsertStartHook) Apply(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool
}

// load our contacts by uuid
contactIDs, err := models.ContactIDsFromReferences(ctx, tx, oa.OrgID(), event.Contacts)
contactIDs, err := models.GetContactIDsFromReferences(ctx, tx, oa.OrgID(), event.Contacts)
if err != nil {
return errors.Wrapf(err, "error loading contacts by reference")
}
Expand Down
35 changes: 27 additions & 8 deletions models/contacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ type URNID null.Int
// ContactID is our type for contact ids, which can be null
type ContactID null.Int

// URN priority constants
const (
topURNPriority = 1000
defaultURNPriority = 0
)

// nil versions of ID types
const (
NilURNID = URNID(0)
NilContactID = ContactID(0)
)
Expand Down Expand Up @@ -83,7 +87,8 @@ func LoadContact(ctx context.Context, db Queryer, org *OrgAssets, id ContactID)
return contacts[0], nil
}

// LoadContacts loads a set of contacts for the passed in ids
// LoadContacts loads a set of contacts for the passed in ids. Note that the order of the returned contacts
// won't necessarily match the order of the ids.
func LoadContacts(ctx context.Context, db Queryer, org *OrgAssets, ids []ContactID) ([]*Contact, error) {
start := time.Now()

Expand Down Expand Up @@ -164,7 +169,7 @@ func LoadContacts(ctx context.Context, db Queryer, org *OrgAssets, ids []Contact

// LoadContactsByUUID loads a set of contacts for the passed in UUIDs
func LoadContactsByUUID(ctx context.Context, db Queryer, oa *OrgAssets, uuids []flows.ContactUUID) ([]*Contact, error) {
ids, err := ContactIDsFromUUIDs(ctx, db, oa.OrgID(), uuids)
ids, err := getContactIDsFromUUIDs(ctx, db, oa.OrgID(), uuids)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -192,26 +197,28 @@ func GetNewestContactModifiedOn(ctx context.Context, db *sqlx.DB, org *OrgAssets
return nil, nil
}

// ContactIDsFromReferences queries the contacts for the passed in org, returning the contact ids for the references
func ContactIDsFromReferences(ctx context.Context, tx Queryer, orgID OrgID, refs []*flows.ContactReference) ([]ContactID, error) {
// GetContactIDsFromReferences gets the contact ids for the given org and set of references. Note that the order of the returned contacts
// won't necessarily match the order of the references.
func GetContactIDsFromReferences(ctx context.Context, tx Queryer, orgID OrgID, refs []*flows.ContactReference) ([]ContactID, error) {
// build our list of UUIDs
uuids := make([]flows.ContactUUID, len(refs))
for i := range refs {
uuids[i] = refs[i].UUID
}

return ContactIDsFromUUIDs(ctx, tx, orgID, uuids)
return getContactIDsFromUUIDs(ctx, tx, orgID, uuids)
}

// ContactIDsFromUUIDs queries the contacts for the passed in org, returning the contact ids for the UUIDs
func ContactIDsFromUUIDs(ctx context.Context, tx Queryer, orgID OrgID, uuids []flows.ContactUUID) ([]ContactID, error) {
// gets the contact IDs for the passed in org and set of UUIDs
func getContactIDsFromUUIDs(ctx context.Context, tx Queryer, orgID OrgID, uuids []flows.ContactUUID) ([]ContactID, error) {
ids, err := queryContactIDs(ctx, tx, `SELECT id FROM contacts_contact WHERE org_id = $1 AND uuid = ANY($2) AND is_active = TRUE`, orgID, pq.Array(uuids))
if err != nil {
return nil, errors.Wrapf(err, "error selecting contact ids by UUID")
}
return ids, nil
}

// utility to query contact IDs
func queryContactIDs(ctx context.Context, tx Queryer, query string, args ...interface{}) ([]ContactID, error) {
ids := make([]ContactID, 0, 10)
rows, err := tx.QueryxContext(ctx, query, args...)
Expand Down Expand Up @@ -631,8 +638,19 @@ func contactIDsFromURNs(ctx context.Context, db *sqlx.DB, orgID OrgID, urnz []ur

// CreateContact creates a new contact for the passed in org with the passed in URNs
func CreateContact(ctx context.Context, db *sqlx.DB, oa *OrgAssets, userID UserID, name string, language envs.Language, urnz []urns.URN) (*Contact, *flows.Contact, error) {
// find current owners of these URNs
owners, err := contactIDsFromURNs(ctx, db, oa.OrgID(), urnz)
if err != nil {
return nil, nil, errors.Wrapf(err, "error looking up contacts for URNs")
}

if len(uniqueContactIDs(owners)) > 0 {
return nil, nil, errors.New("URNs in use by other contacts")
}

contactID, err := tryInsertContactAndURNs(ctx, db, oa.OrgID(), userID, name, language, urnz)
if err != nil {
// always possible that another thread created a contact with these URNs after we checked above
if dbutil.IsUniqueViolation(err) {
return nil, nil, errors.New("URNs in use by other contacts")
}
Expand Down Expand Up @@ -748,7 +766,8 @@ func uniqueContactIDs(urnMap map[urns.URN]ContactID) []ContactID {
return unique
}

// tries to create a new contact for the passed in org with the passed in URNs
// Tries to create a new contact for the passed in org with the passed in URNs. Returned error can be tested with `dbutil.IsUniqueViolation` to
// determine if problem was one or more of the URNs already exist and are assigned to other contacts.
func tryInsertContactAndURNs(ctx context.Context, db *sqlx.DB, orgID OrgID, userID UserID, name string, language envs.Language, urnz []urns.URN) (ContactID, error) {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions models/contacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,18 @@ func TestGetOrCreateContact(t *testing.T) {
}
}

func TestGetContactIDsFromReferences(t *testing.T) {
ctx := testsuite.CTX()
db := testsuite.DB()

ids, err := models.GetContactIDsFromReferences(ctx, db, models.Org1, []*flows.ContactReference{
flows.NewContactReference(models.CathyUUID, "Cathy"),
flows.NewContactReference(models.BobUUID, "Bob"),
})
require.NoError(t, err)
assert.ElementsMatch(t, []models.ContactID{models.CathyID, models.BobID}, ids)
}

func TestStopContact(t *testing.T) {
ctx := testsuite.CTX()
db := testsuite.DB()
Expand Down
2 changes: 1 addition & 1 deletion models/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ func NewBroadcastFromEvent(ctx context.Context, tx Queryer, org *OrgAssets, even
}

// resolve our contact references
contactIDs, err := ContactIDsFromReferences(ctx, tx, org.OrgID(), event.Contacts)
contactIDs, err := GetContactIDsFromReferences(ctx, tx, org.OrgID(), event.Contacts)
if err != nil {
return nil, errors.Wrapf(err, "error resolving contact references")
}
Expand Down
4 changes: 2 additions & 2 deletions web/contact/testdata/create.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@
"status": 200,
"response": {
"contact": {
"uuid": "c34b6c7d-fa06-4563-92a3-d648ab64bccb",
"id": 30003,
"uuid": "8720f157-ca1c-432f-9c0b-2014ddc77094",
"id": 30002,
"name": "María",
"status": "active",
"timezone": "America/Los_Angeles",
Expand Down

0 comments on commit daed23a

Please sign in to comment.