Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #240 from vxio/239-fix-update-phones
Browse files Browse the repository at this point in the history
fix: bug in updating a customer's phones and addresses
  • Loading branch information
vxio committed Oct 12, 2020
2 parents ea53223 + 8a2a2fd commit 8d7ebd3
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 259 deletions.
8 changes: 7 additions & 1 deletion api/client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,16 @@ paths:
type: string
- name: count
in: query
description: Optional parameter for searching for customers by specifying the amount to return
description: Optional parameter for searching by specifying the amount to return
example: 20
schema:
type: string
- name: customerIDs
in: query
description: Optional parameter for searching by customers' IDs
example: e210a9d6-d755-4455-9bd2-9577ea7e1081,970ef15d-a4e1-473f-b5d7-da38163b0ba3
schema:
type: string
responses:
'200':
description: Customers were successfully retrieved
Expand Down
13 changes: 11 additions & 2 deletions pkg/client/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ paths:
schema:
type: string
style: form
- description: Optional parameter for searching for customers by specifying
the amount to return
- description: Optional parameter for searching by specifying the amount to
return
example: 20
explode: true
in: query
Expand All @@ -240,6 +240,15 @@ paths:
schema:
type: string
style: form
- description: Optional parameter for searching by customers' IDs
example: e210a9d6-d755-4455-9bd2-9577ea7e1081,970ef15d-a4e1-473f-b5d7-da38163b0ba3
explode: true
in: query
name: customerIDs
required: false
schema:
type: string
style: form
responses:
"200":
content:
Expand Down
19 changes: 12 additions & 7 deletions pkg/client/api_customers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2517,12 +2517,13 @@ func (a *CustomersApiService) ReplaceCustomerMetadata(ctx _context.Context, cust

// SearchCustomersOpts Optional parameters for the method 'SearchCustomers'
type SearchCustomersOpts struct {
Query optional.String
Email optional.String
Status optional.String
Type_ optional.String
Skip optional.String
Count optional.String
Query optional.String
Email optional.String
Status optional.String
Type_ optional.String
Skip optional.String
Count optional.String
CustomerIDs optional.String
}

/*
Expand All @@ -2535,7 +2536,8 @@ Search for customers using different filter parameters
* @param "Status" (optional.String) - Optional parameter for searching by customer status
* @param "Type_" (optional.String) - Optional parameter for searching by customer type
* @param "Skip" (optional.String) - Optional parameter for searching for customers by skipping over an initial group
* @param "Count" (optional.String) - Optional parameter for searching for customers by specifying the amount to return
* @param "Count" (optional.String) - Optional parameter for searching by specifying the amount to return
* @param "CustomerIDs" (optional.String) - Optional parameter for searching by customers' IDs
@return []Customer
*/
func (a *CustomersApiService) SearchCustomers(ctx _context.Context, localVarOptionals *SearchCustomersOpts) ([]Customer, *_nethttp.Response, error) {
Expand Down Expand Up @@ -2572,6 +2574,9 @@ func (a *CustomersApiService) SearchCustomers(ctx _context.Context, localVarOpti
if localVarOptionals != nil && localVarOptionals.Count.IsSet() {
localVarQueryParams.Add("count", parameterToString(localVarOptionals.Count.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.CustomerIDs.IsSet() {
localVarQueryParams.Add("customerIDs", parameterToString(localVarOptionals.CustomerIDs.Value(), ""))
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}

Expand Down
3 changes: 2 additions & 1 deletion pkg/client/docs/CustomersApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -1218,7 +1218,8 @@ Name | Type | Description | Notes
**status** | **optional.String**| Optional parameter for searching by customer status |
**type_** | **optional.String**| Optional parameter for searching by customer type |
**skip** | **optional.String**| Optional parameter for searching for customers by skipping over an initial group |
**count** | **optional.String**| Optional parameter for searching for customers by specifying the amount to return |
**count** | **optional.String**| Optional parameter for searching by specifying the amount to return |
**customerIDs** | **optional.String**| Optional parameter for searching by customers' IDs |

### Return type

Expand Down
226 changes: 200 additions & 26 deletions pkg/customers/customer_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package customers

import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -13,6 +14,7 @@ import (
moovhttp "github.com/moov-io/base/http"

"github.com/moov-io/base/log"

"github.com/moov-io/customers/pkg/client"
"github.com/moov-io/customers/pkg/route"
)
Expand All @@ -26,7 +28,7 @@ func searchCustomers(logger log.Logger, repo CustomerRepository) http.HandlerFun
return
}

params, err := readSearchParams(r)
params, err := parseSearchParams(r)
if err != nil {
moovhttp.Problem(w, err)
return
Expand Down Expand Up @@ -55,15 +57,25 @@ type SearchParams struct {
Type string
Skip int64
Count int64
CustomerIDs []string
}

func readSearchParams(r *http.Request) (SearchParams, error) {
func parseSearchParams(r *http.Request) (SearchParams, error) {
queryParams := r.URL.Query()
getQueryParam := func(key string) string {
return strings.ToLower(strings.TrimSpace(queryParams.Get(key)))
}
params := SearchParams{
Query: strings.ToLower(strings.TrimSpace(r.URL.Query().Get("query"))),
Email: strings.ToLower(strings.TrimSpace(r.URL.Query().Get("email"))),
Status: strings.ToLower(strings.TrimSpace(r.URL.Query().Get("status"))),
Type: strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type"))),
Query: getQueryParam("query"),
Email: getQueryParam("email"),
Status: getQueryParam("status"),
Type: getQueryParam("type"),
}
customerIDsInput := getQueryParam("customerIDs")
if customerIDsInput != "" {
params.CustomerIDs = strings.Split(customerIDsInput, ",")
}

skip, count, exists, err := moovhttp.GetSkipAndCount(r)
if exists && err != nil {
return params, err
Expand All @@ -84,54 +96,103 @@ func (r *sqlCustomerRepository) searchCustomers(params SearchParams) ([]*client.
}
defer stmt.Close()

var customerIDs []string
customers := make([]*client.Customer, 0)

// grab customerIDs
rows, err := stmt.Query(args...)
if err != nil {
return customers, err
return nil, err
}
for rows.Next() {
var customerID string
if err := rows.Scan(&customerID); err == nil {
customerIDs = append(customerIDs, customerID)
} else {
return customers, err
var c client.Customer
var birthDate *string
err := rows.Scan(
&c.CustomerID,
&c.FirstName,
&c.MiddleName,
&c.LastName,
&c.NickName,
&c.Suffix,
&c.Type,
&birthDate,
&c.Status,
&c.Email,
&c.CreatedAt,
&c.LastModified,
)
if err != nil {
return nil, err
}
customers = append(customers, &c)
}

// Read each Customer
for i := range customerIDs {
cust, err := r.GetCustomer(customerIDs[i])
if err != nil {
return customers, fmt.Errorf("search: customerID=%s error=%v", customerIDs[i], err)
}
customers = append(customers, cust)
if len(customers) == 0 {
return customers, nil
}

var customerIDs []string
for _, c := range customers {
customerIDs = append(customerIDs, c.CustomerID)
}

phonesByCustomerID, err := r.getPhones(customerIDs)
if err != nil {
return nil, fmt.Errorf("fetching customer phones: %v", err)
}
addressesByCustomerID, err := r.getAddresses(customerIDs)
if err != nil {
return nil, fmt.Errorf("fetching customer addresses: %v", err)
}
metadataByCustomerID, err := r.getMetadata(customerIDs)
if err != nil {
return nil, fmt.Errorf("fetching customer metadata: %v", err)
}

for _, c := range customers {
c.Phones = phonesByCustomerID[c.CustomerID]
c.Addresses = addressesByCustomerID[c.CustomerID]
c.Metadata = metadataByCustomerID[c.CustomerID].Metadata
}

return customers, nil
}

func buildSearchQuery(params SearchParams) (string, []interface{}) {
var args []interface{}
query := `select customer_id from customers where deleted_at is null and organization = ?`
args = append(args, params.Organization)
query := `select customer_id, first_name, middle_name, last_name, nick_name, suffix, type, birth_date, status, email, created_at, last_modified
from customers where deleted_at is null`

if params.Organization != "" {
query += " and organization = ?"
args = append(args, params.Organization)
}

if params.Query != "" {
query += " and lower(first_name) || \" \" || lower(last_name) LIKE ?"
args = append(args, "%"+params.Query+"%")
// warning: this will ONLY work for MySQL
query += " and lower(concat(first_name,' ', last_name)) LIKE ?"
args = append(args, fmt.Sprintf("%%%s%%", params.Query))
}

if params.Email != "" {
query += " and lower(email) like ?"
args = append(args, "%"+params.Email)
}

if params.Status != "" {
query += " and status like ?"
args = append(args, "%"+params.Status)
}

if params.Type != "" {
query += " and type like ?"
args = append(args, "%"+params.Type)
}

if len(params.CustomerIDs) > 0 {
query += fmt.Sprintf(" and customer_id in (?%s)", strings.Repeat(",?", len(params.CustomerIDs)-1))
for _, id := range params.CustomerIDs {
args = append(args, id)
}
}

query += " order by created_at desc limit ?"
args = append(args, fmt.Sprintf("%d", params.Count))

Expand All @@ -142,3 +203,116 @@ func buildSearchQuery(params SearchParams) (string, []interface{}) {
query += ";"
return query, args
}

func (r *sqlCustomerRepository) getPhones(customerIDs []string) (map[string][]client.Phone, error) {
query := fmt.Sprintf(
"select customer_id, number, valid, type from customers_phones where customer_id in (?%s)",
strings.Repeat(",?", len(customerIDs)-1),
)

rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
if err != nil {
return nil, err
}
defer rows.Close()

ret := make(map[string][]client.Phone)
for rows.Next() {
var p client.Phone
var customerID string
err := rows.Scan(
&customerID,
&p.Number,
&p.Valid,
&p.Type,
)
if err != nil {
return nil, fmt.Errorf("scanning row: %v", err)
}
ret[customerID] = append(ret[customerID], p)
}

return ret, nil
}

func (r *sqlCustomerRepository) getAddresses(customerIDs []string) (map[string][]client.CustomerAddress, error) {
query := fmt.Sprintf(
"select customer_id, address_id, type, address1, address2, city, state, postal_code, country, validated from customers_addresses where customer_id in (?%s) and deleted_at is null;",
strings.Repeat(",?", len(customerIDs)-1),
)
rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
if err != nil {
return nil, err
}
defer rows.Close()

ret := make(map[string][]client.CustomerAddress)
for rows.Next() {
var a client.CustomerAddress
var customerID string
if err := rows.Scan(
&customerID,
&a.AddressID,
&a.Type,
&a.Address1,
&a.Address2,
&a.City,
&a.State,
&a.PostalCode,
&a.Country,
&a.Validated,
); err != nil {
return nil, fmt.Errorf("scanning row: %v", err)
}
ret[customerID] = append(ret[customerID], a)
}

return ret, nil
}

func (r *sqlCustomerRepository) getMetadata(customerIDs []string) (map[string]client.CustomerMetadata, error) {
query := fmt.Sprintf(
"select customer_id, meta_key, meta_value from customer_metadata where customer_id in (?%s);",
strings.Repeat(",?", len(customerIDs)-1),
)
rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
if err != nil {
return nil, err
}
defer rows.Close()

ret := make(map[string]client.CustomerMetadata)
for rows.Next() {
m := client.CustomerMetadata{
Metadata: make(map[string]string),
}
var customerID string
var k, v string
if err := rows.Scan(&customerID, &k, &v); err != nil {
return nil, fmt.Errorf("scanning row: %v", err)
}
m.Metadata[k] = v
ret[customerID] = m
}
return ret, nil
}

func (r *sqlCustomerRepository) queryRowsByCustomerIDs(query string, customerIDs []string) (*sql.Rows, error) {
stmt, err := r.db.Prepare(query)
if err != nil {
return nil, fmt.Errorf("preparing query: %v", err)
}
defer stmt.Close()

var args []interface{}
for _, id := range customerIDs {
args = append(args, id)
}

rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("executing query: %v", err)
}

return rows, nil
}
Loading

0 comments on commit 8d7ebd3

Please sign in to comment.