diff --git a/README.md b/README.md index fe09f9e..1be8bfb 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,8 @@ Two endpoints so far, both accepting `addr` parameter. } ], "Address": { - "ACRateAccountKey": "12342478585", - "Address": "500 Queen Street, Auckland Central", - "Suggestion": "500 Queen Street, Auckland Central" + "ID": "12342478585", + "Address": "500 Queen Street, Auckland Central" } } diff --git a/addr.go b/addr.go index 8b71e74..5ba0a5d 100644 --- a/addr.go +++ b/addr.go @@ -1,73 +1,85 @@ package aklapi import ( - "bytes" "encoding/json" "errors" "log" "net/http" + "strconv" "time" ) var ( // defined as a variable so it can be overridden in tests. - addrURI = `https://www.aucklandcouncil.govt.nz/_vti_bin/ACWeb/ACservices.svc/GetMatchingPropertyAddresses` + addrURI = `https://www.aucklandcouncil.govt.nz/nextapi/property` ) // AddrRequest is the address request. type AddrRequest struct { - ResultCount int `json:"ResultCount"` - SearchText string `json:"SearchText"` - RateKeyRequired bool `json:"RateKeyRequired"` + PageSize int + SearchText string } -// AddrResponse is the address response. -type AddrResponse []Address - -// AddrSuggestion is the address suggestion. +// Address is the address and its unique identifier (rate account key). type Address struct { - ACRateAccountKey string `json:"ACRateAccountKey"` - Address string `json:"Address"` - Suggestion string `json:"Suggestion"` + ID string `json:"ID"` + Address string `json:"Address"` +} + +// AddrResponse is the address response. +type AddrResponse struct { + Items []Address `json:"items"` } func (s Address) String() string { - return "<" + s.Address + " (" + s.ACRateAccountKey + ")>" + return "<" + s.Address + " (" + s.ID + ")>" } // AddressLookup is a convenience function to get addresses. -func AddressLookup(addr string) (AddrResponse, error) { - return MatchingPropertyAddresses(&AddrRequest{SearchText: addr, RateKeyRequired: false, ResultCount: 10}) +func AddressLookup(addr string) (*AddrResponse, error) { + return MatchingPropertyAddresses(&AddrRequest{SearchText: addr, PageSize: 10}) } // MatchingPropertyAddresses wrapper around the AKL Council API. -func MatchingPropertyAddresses(addrReq *AddrRequest) (AddrResponse, error) { +func MatchingPropertyAddresses(addrReq *AddrRequest) (*AddrResponse, error) { cachedAr, ok := addrCache.Lookup(addrReq.SearchText) if ok { log.Printf("cached address result: %q", cachedAr) return cachedAr, nil } - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - if err := enc.Encode(addrReq); err != nil { + + req, err := http.NewRequest("GET", addrURI, nil) + if err != nil { return nil, err } + q := req.URL.Query() + q.Add("query", addrReq.SearchText) + if addrReq.PageSize > 0 { + q.Add("pageSize", strconv.Itoa(addrReq.PageSize)) + } + req.URL.RawQuery = q.Encode() start := time.Now() - resp, err := http.Post(addrURI, "application/json; charset=UTF-8", &buf) + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() log.Printf("address call complete in %s", time.Since(start)) + if resp.StatusCode != http.StatusOK { + return nil, errors.New("address API returned status code: " + strconv.Itoa(resp.StatusCode)) + } + dec := json.NewDecoder(resp.Body) - ar := AddrResponse{} - if err := dec.Decode(&ar); err != nil { + var apiResp AddrResponse + if err := dec.Decode(&apiResp); err != nil { return nil, err } - addrCache.Add(addrReq.SearchText, ar) - return ar, nil + + addrCache.Add(addrReq.SearchText, &apiResp) + return &apiResp, nil } func oneAddress(addr string) (*Address, error) { @@ -76,8 +88,8 @@ func oneAddress(addr string) (*Address, error) { return nil, err } // need exactly one address to continue - if len(resp) != 1 { + if len(resp.Items) != 1 { return nil, errors.New("ambiguous or empty address results") } - return &resp[0], nil + return &resp.Items[0], nil } diff --git a/addr_cache.go b/addr_cache.go index 1ff2530..240ef09 100644 --- a/addr_cache.go +++ b/addr_cache.go @@ -1,10 +1,10 @@ package aklapi -type addrResponseCache map[string]AddrResponse +type addrResponseCache map[string]*AddrResponse var addrCache = make(addrResponseCache) -func (c addrResponseCache) Lookup(searchText string) (resp AddrResponse, ok bool) { +func (c addrResponseCache) Lookup(searchText string) (resp *AddrResponse, ok bool) { if NoCache { return nil, false } @@ -12,6 +12,6 @@ func (c addrResponseCache) Lookup(searchText string) (resp AddrResponse, ok bool return } -func (c addrResponseCache) Add(searchText string, ar AddrResponse) { +func (c addrResponseCache) Add(searchText string, ar *AddrResponse) { c[searchText] = ar } diff --git a/addr_cache_test.go b/addr_cache_test.go index 2bf79c3..3b25fe4 100644 --- a/addr_cache_test.go +++ b/addr_cache_test.go @@ -19,13 +19,13 @@ func Test_addrResponseCache_Lookup(t *testing.T) { NoCache bool // if true, set NoCache to true before running test c addrResponseCache args args - wantResp AddrResponse + wantResp *AddrResponse wantOk bool }{ {"not in cache", false, addrResponseCache{ - "xxx": AddrResponse{*testAddr}, + "xxx": &AddrResponse{Items: []Address{*testAddr}}, }, args{"yyy"}, nil, @@ -34,16 +34,16 @@ func Test_addrResponseCache_Lookup(t *testing.T) { {"cached", false, addrResponseCache{ - testAddr.Address: AddrResponse{*testAddr}, + testAddr.Address: &AddrResponse{Items: []Address{*testAddr}}, }, args{testAddr.Address}, - AddrResponse{*testAddr}, + &AddrResponse{Items: []Address{*testAddr}}, true, }, {"cached, no cache mode", true, addrResponseCache{ - testAddr.Address: AddrResponse{*testAddr}, + testAddr.Address: &AddrResponse{Items: []Address{*testAddr}}, }, args{testAddr.Address}, nil, @@ -67,7 +67,7 @@ func Test_addrResponseCache_Lookup(t *testing.T) { func Test_addrResponseCache_Add(t *testing.T) { type args struct { searchText string - ar AddrResponse + ar *AddrResponse } tests := []struct { name string @@ -77,9 +77,9 @@ func Test_addrResponseCache_Add(t *testing.T) { }{ {"add", addrResponseCache{}, - args{testAddr.Address, AddrResponse{*testAddr}}, + args{testAddr.Address, &AddrResponse{Items: []Address{*testAddr}}}, addrResponseCache{ - testAddr.Address: AddrResponse{*testAddr}, + testAddr.Address: &AddrResponse{Items: []Address{*testAddr}}, }, }, } diff --git a/addr_test.go b/addr_test.go index 9235de9..ca345aa 100644 --- a/addr_test.go +++ b/addr_test.go @@ -10,9 +10,8 @@ import ( ) var testAddr = &Address{ - ACRateAccountKey: "42", - Address: "Red Square", - Suggestion: "Red Square", + ID: "42", + Address: "Red Square", } func TestMatchingPropertyAddresses(t *testing.T) { @@ -23,21 +22,29 @@ func TestMatchingPropertyAddresses(t *testing.T) { name string testSrv *httptest.Server args args - want AddrResponse + want *AddrResponse wantErr bool }{ {"main branch", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeAddrJSON(w, AddrResponse{*testAddr}) + writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}}) })), args{&AddrRequest{ - ResultCount: 1, - SearchText: "red sq", - RateKeyRequired: false, + PageSize: 1, + SearchText: "red sq", }}, - AddrResponse{*testAddr}, + &AddrResponse{Items: []Address{*testAddr}}, false, }, + {"non-200 status code", + httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + writeAddrJSON(w, AddrResponse{}) + })), + args{&AddrRequest{}}, + nil, + true, + }, {"post error", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) @@ -81,15 +88,15 @@ func TestAddress(t *testing.T) { name string testSrv *httptest.Server args args - want AddrResponse + want *AddrResponse wantErr bool }{ {"main branch", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeAddrJSON(w, AddrResponse{*testAddr}) + writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}}) })), args{"red square"}, - AddrResponse{*testAddr}, + &AddrResponse{Items: []Address{*testAddr}}, false, }, } @@ -124,7 +131,7 @@ func Test_oneAddress(t *testing.T) { }{ {"one address", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeAddrJSON(w, AddrResponse{*testAddr}) + writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr}}) })), args{"red square"}, testAddr, @@ -132,7 +139,7 @@ func Test_oneAddress(t *testing.T) { }, {"several address", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeAddrJSON(w, AddrResponse{*testAddr, *testAddr}) + writeAddrJSON(w, AddrResponse{Items: []Address{*testAddr, *testAddr}}) })), args{"red squarex"}, nil, @@ -140,7 +147,7 @@ func Test_oneAddress(t *testing.T) { }, {"no address", httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("[]")) + w.Write([]byte("{\"items\":[]}")) })), args{"red squarec"}, nil, diff --git a/rubbish.go b/rubbish.go index 8c214cb..8e670a7 100644 --- a/rubbish.go +++ b/rubbish.go @@ -101,7 +101,7 @@ func CollectionDayDetail(addr string) (*CollectionDayDetailResult, error) { return nil, err } start := time.Now() - result, err := fetchandparse(address.ACRateAccountKey) + result, err := fetchandparse(address.ID) if err != nil { return nil, err } @@ -112,8 +112,8 @@ func CollectionDayDetail(addr string) (*CollectionDayDetailResult, error) { } // fetchandparse retrieves the data from the webpage and attempts to parse it. -func fetchandparse(ACRateAccountKey string) (*CollectionDayDetailResult, error) { - resp, err := http.Get(fmt.Sprintf(collectionDayURI, ACRateAccountKey)) +func fetchandparse(addressID string) (*CollectionDayDetailResult, error) { + resp, err := http.Get(fmt.Sprintf(collectionDayURI, addressID)) if err != nil { return nil, err } diff --git a/rubbish_test.go b/rubbish_test.go index 85df952..fd913fd 100644 --- a/rubbish_test.go +++ b/rubbish_test.go @@ -42,22 +42,22 @@ func Test_parse(t *testing.T) { &CollectionDayDetailResult{ Collections: []RubbishCollection{ { - Day: "Tuesday, 18 November", - Date: adjustYear(time.Date(0, 11, 18, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 16 December", + Date: adjustYear(time.Date(0, 12, 16, 0, 0, 0, 0, defaultLoc)), Rubbish: true, Recycle: false, FoodScraps: false, }, { - Day: "Tuesday, 18 November", - Date: adjustYear(time.Date(0, 11, 18, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 16 December", + Date: adjustYear(time.Date(0, 12, 16, 0, 0, 0, 0, defaultLoc)), Rubbish: false, Recycle: false, FoodScraps: true, }, { - Day: "Tuesday, 25 November", - Date: adjustYear(time.Date(0, 11, 25, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 23 December", + Date: adjustYear(time.Date(0, 12, 23, 0, 0, 0, 0, defaultLoc)), Rubbish: false, Recycle: true, FoodScraps: false, @@ -71,14 +71,14 @@ func Test_parse(t *testing.T) { &CollectionDayDetailResult{ Collections: []RubbishCollection{ { - Day: "Saturday, 15 November", - Date: adjustYear(time.Date(0, 11, 15, 0, 0, 0, 0, defaultLoc)), + Day: "Friday, 12 December", + Date: adjustYear(time.Date(0, 12, 12, 0, 0, 0, 0, defaultLoc)), Rubbish: true, Recycle: false, }, { - Day: "Saturday, 15 November", - Date: adjustYear(time.Date(0, 11, 15, 0, 0, 0, 0, defaultLoc)), + Day: "Friday, 12 December", + Date: adjustYear(time.Date(0, 12, 12, 0, 0, 0, 0, defaultLoc)), Rubbish: false, Recycle: true, }, @@ -116,31 +116,30 @@ func TestCollectionDayDetail(t *testing.T) { &CollectionDayDetailResult{ Collections: []RubbishCollection{ { - Day: "Tuesday, 18 November", - Date: adjustYear(time.Date(0, 11, 18, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 16 December", + Date: adjustYear(time.Date(0, 12, 16, 0, 0, 0, 0, defaultLoc)), Rubbish: true, Recycle: false, FoodScraps: false, }, { - Day: "Tuesday, 18 November", - Date: adjustYear(time.Date(0, 11, 18, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 16 December", + Date: adjustYear(time.Date(0, 12, 16, 0, 0, 0, 0, defaultLoc)), Rubbish: false, Recycle: false, FoodScraps: true, }, { - Day: "Tuesday, 25 November", - Date: adjustYear(time.Date(0, 11, 25, 0, 0, 0, 0, defaultLoc)), + Day: "Tuesday, 23 December", + Date: adjustYear(time.Date(0, 12, 23, 0, 0, 0, 0, defaultLoc)), Rubbish: false, Recycle: true, FoodScraps: false, }, }, Address: &Address{ - ACRateAccountKey: "42", - Address: "Red Square", - Suggestion: "Red Square", + ID: "42", + Address: "Red Square", }, }, false, @@ -285,7 +284,7 @@ func TestRubbishCollection_parseDate(t *testing.T) { func testMux() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/addr", func(w http.ResponseWriter, r *http.Request) { - data, err := json.Marshal(AddrResponse{*testAddr}) + data, err := json.Marshal(AddrResponse{Items: []Address{*testAddr}}) if err != nil { panic(err) } diff --git a/test_assets/1-luanda-drive.html b/test_assets/1-luanda-drive.html index da9eeee..d8a3fe7 100644 --- a/test_assets/1-luanda-drive.html +++ b/test_assets/1-luanda-drive.html @@ -1,29 +1,29 @@ -Your collection daySkip to main content

Ngā kōrero kohinga mōuYour collection day

1 Luanda Drive
Ranui, Auckland 0612

Household collection

Your next collection dates

Rubbish: Tuesday, 18 November


Food scraps: Tuesday, 18 November


Recycling: Tuesday, 25 November


Put bins out the night before or before 7am.

Where you can put your rubbish, food scraps and recycling for collection

Household rubbish

Put your rubbish bin out on the kerbside (not on the footpath or road) before 7am on your collection day.

Food scraps

Put your food scraps bin on the kerbside before 7am on your collection day, or the night before.

Lock your food scraps bin by placing the black handle in upright position. Put your food scraps bins next to your rubbish and recycling bins with enough space for our collection trucks to safely empty bins.

If you share the berm with your neighbours, you should place the food scraps bin in a cluster or group. For more information, see How to use your food scraps bin.

Household recycling

We only collect recycling from our official yellow-lidded green-bodied wheelie bins.

Put your recycling bin out on the kerbside (not on the footpath or road) before 7am on your collection day.

The bin is the property of Auckland Council and needs to stay with this property if you move.

These bins cannot be used for commercial quantities of recycling.

Commercial rubbish

Put your rubbish bin out on the kerbside (not on the footpath or road) before 7am on your collection day.

Commercial recycling

All properties are supplied with one yellow-lidded green-bodied recycling bin for household recycling collections.

Put your recycling bin out on the kerbside (not on the footpath or road) before 7am on your collection day.

The bin is the property of Auckland Council and needs to stay with this property if you move.

These bins cannot be used for commercial quantities of recycling.

\ No newline at end of file diff --git a/test_assets/500-queen-street.html b/test_assets/500-queen-street.html index 2605d12..98f4085 100644 --- a/test_assets/500-queen-street.html +++ b/test_assets/500-queen-street.html @@ -1,4 +1,4 @@ -Your collection daySkip to main content

Ngā kōrero kohinga mōuYour collection day

500 Queen Street
Auckland Central, Auckland 1010

Household collection

Your next collection dates

Rubbish: Saturday, 15 November


Food scraps:


Recycling: Saturday, 15 November


Put bags out before collection times: Monday to Saturday, 5pm to 5.30pm and 12am to 4am
Sunday after 12am only

Where you can put your rubbish, food scraps and recycling for collection

Household rubbish

Put your rubbish in council supplied orange rubbish bags on the kerbside just before your collection time.

Properties in the inner CBD are supplied with 52 orange rubbish bags every six months.

Food scraps

This property is listed as out of food scraps service area and will not receive collection service.

Household recycling

Put all recycling on the kerb just before your collection time.

Properties in the inner CBD are supplied with 78 clear recycling bags every six months.

Paper and cardboard

Paper

Put paper in council supplied clear recycling bags. This includes:

  • newspaper
  • mail
  • magazines
  • receipts
  • office
  • paper
  • Tetra Pak cartons.

The bag may indicate a different message but will change when new bags are made.

Cardboard

Put cardboard separate from the recycling bag. It should be bound or stacked together.

General recycling

Put all non-cardboard recycling in the clear recycling bag.

Commercial rubbish

Put your rubbish in council supplied orange rubbish bags on the kerbside just before your collection time.

Properties in the inner CBD are supplied with 52 orange rubbish bags every six months.

Commercial recycling

Put all recycling on the kerb just before your collection time.

Properties in the inner CBD are supplied with 156 clear recycling bags annually at 6 monthly intervals.

Paper and cardboard

Paper

Put paper in the clear recycling bag. This includes:

  • newspaper
  • mail
  • magazines
  • receipts
  • office
  • paper
  • Tetra Pak cartons.

The bag may indicate a different message but will change when new bags are made.

Cardboard

Put cardboard separate from the recycling bag. It should be bound or stacked together.

General recycling

Put all non-cardboard recycling in the clear recycling bag.

\ No newline at end of file