This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new e52aaa2258 Change /coordinates timestamps to RFC3339 (#7563)
e52aaa2258 is described below

commit e52aaa22580112358d1fbf393f913c56726aa15f
Author: ocket8888 <[email protected]>
AuthorDate: Thu Jun 8 09:04:48 2023 -0600

    Change /coordinates timestamps to RFC3339 (#7563)
    
    * Add/update/correct GoDoc comments
    
    * Add APIv5 representations of Coordinates and related structures
    
    Also added deprecation notices to older types.
    
    * Remove redundant types from map composite literal
    
    * Make receiver names consistent
    
    ...and also remove a super-long copypasta comment noting features of the
    language in which this program is written.
    
    * Add v5 /coordinates GET handler and routing
    
    Also put a deprecation notice on the old, "CRUDer" method
    
    * Add a Stringer method for API version
    
    * Add v5 /coordinates POST handler and routing
    
    Also put a deprecation notice on the old, "CRUDer" method(s)
    
    * Add v5 /coordinates PUT handler and routing
    
    Also put a deprecation notice on the old, "CRUDer" method(s)
    
    * Add v5 /coordinates DELETE handler and routing
    
    * Add a bunch of deprecation notices.
    
    * fix tests
    
    * update alert messages for consistency
    
    * add changelogs
    
    * Update docs
    
    * update client
    
    * fix broken v5 client
    
    * Fix response codes when requesting non-existent Coordinate
    
    * add a utility function for writing a not modified response
    
    * add IMS functionality to GET handler
    
    * add IUS/ETag support to PUT handler
    
    * fix GET IMS not supported when using ordering/pagination
---
 docs/source/api/v5/coordinates.rst                 |  49 ++-
 lib/go-tc/coordinates.go                           |  42 +++
 traffic_ops/testing/api/v5/coordinates_test.go     |  47 +--
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 traffic_ops/traffic_ops_golang/api/api.go          |  18 +
 traffic_ops/traffic_ops_golang/api/api_test.go     |  12 +
 .../traffic_ops_golang/coordinate/coordinates.go   | 386 +++++++++++++++++++--
 .../coordinate/coordinates_test.go                 |  10 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |   8 +-
 traffic_ops/v5-client/coordinate.go                |  28 +-
 traffic_ops/v5-client/origin.go                    |   2 +-
 11 files changed, 512 insertions(+), 92 deletions(-)

diff --git a/docs/source/api/v5/coordinates.rst 
b/docs/source/api/v5/coordinates.rst
index 6f624cad50..dc966fbeab 100644
--- a/docs/source/api/v5/coordinates.rst
+++ b/docs/source/api/v5/coordinates.rst
@@ -56,7 +56,7 @@ Request Structure
 Response Structure
 ------------------
 :id:          Integral, unique, identifier for this coordinate pair
-:lastUpdated: The time and date at which this entry was last updated, in a 
``ctime``-like format
+:lastUpdated: The time and date at which this entry was last updated, in 
:rfc:3339 format
 :latitude:    Latitude of the coordinate
 :longitude:   Longitude of the coordinate
 :name:        The name of the coordinate - typically this just reflects the 
name of the Cache Group for which the coordinate was created
@@ -82,55 +82,55 @@ Response Structure
                        "name": "from_cachegroup_TRAFFIC_ANALYTICS",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 2,
                        "name": "from_cachegroup_TRAFFIC_OPS",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 3,
                        "name": "from_cachegroup_TRAFFIC_OPS_DB",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 4,
                        "name": "from_cachegroup_TRAFFIC_PORTAL",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 5,
                        "name": "from_cachegroup_TRAFFIC_STATS",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 6,
                        "name": "from_cachegroup_CDN_in_a_Box_Mid",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:04+00"
+                       "lastUpdated": "2018-10-24T16:07:04.596321Z"
                },
                {
                        "id": 7,
                        "name": "from_cachegroup_CDN_in_a_Box_Edge",
                        "latitude": 38.897663,
                        "longitude": -77.036574,
-                       "lastUpdated": "2018-10-24 16:07:05+00"
+                       "lastUpdated": "2018-10-24T16:07:05.596321Z"
                }
        ]}
 
 ``POST``
 ========
-Creates a new coordinate pair
+Creates a new coordinate pair.
 
 :Auth. Required: Yes
 :Roles Required: "admin" or "operations"
@@ -159,7 +159,7 @@ Request Structure
 Response Structure
 ------------------
 :id:          Integral, unique, identifier for the newly created coordinate 
pair
-:lastUpdated: The time and date at which this entry was last updated, in a 
``ctime``-like format
+:lastUpdated: The time and date at which this entry was last updated, in 
:rfc:3339 format
 :latitude:    Latitude of the newly created coordinate
 :longitude:   Longitude of the newly created coordinate
 :name:        The name of the coordinate
@@ -181,7 +181,7 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "coordinate was created.",
+                       "text": "Coordinate 'test' (#9) created",
                        "level": "success"
                }
        ],
@@ -190,7 +190,7 @@ Response Structure
                "name": "test",
                "latitude": 0,
                "longitude": 0,
-               "lastUpdated": "2018-11-15 17:48:55+00"
+               "lastUpdated": "2018-11-15T17:48:55.596321Z"
        }}
 
 
@@ -233,7 +233,7 @@ Request Structure
 Response Structure
 ------------------
 :id:          Integral, unique, identifier for the coordinate pair
-:lastUpdated: The time and date at which this entry was last updated, in a 
``ctime``-like format
+:lastUpdated: The time and date at which this entry was last updated, in 
:rfc:3339 format
 :latitude:    Latitude of the coordinate
 :longitude:   Longitude of the coordinate
 :name:        The name of the coordinate
@@ -255,7 +255,7 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "coordinate was updated.",
+                       "text": "Coordinate 'quest' (#9) updated",
                        "level": "success"
                }
        ],
@@ -264,7 +264,7 @@ Response Structure
                "name": "quest",
                "latitude": 0,
                "longitude": 0,
-               "lastUpdated": "2018-11-15 17:54:30+00"
+               "lastUpdated": "2018-11-15T17:54:30.596321Z"
        }}
 
 ``DELETE``
@@ -274,10 +274,16 @@ Deletes a coordinate
 :Auth. Required: Yes
 :Roles Required: "admin" or "operations"
 :Permissions Required: COORDINATE:DELETE, COORDINATE:READ
-:Response Type:  ``undefined``
+:Response Type:  Object
 
 Request Structure
 -----------------
+:id:          Integral, unique, identifier for the coordinate pair
+:lastUpdated: The time and date at which this entry was last updated, in 
:rfc:3339 format
+:latitude:    Latitude of the coordinate
+:longitude:   Longitude of the coordinate
+:name:        The name of the coordinate
+
 .. table:: Request Query Parameters
 
        
+------+----------+-------------------------------------------------------------+
@@ -305,7 +311,14 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "coordinate was deleted.",
+                       "text": "Coordinate 'quest' (#9) deleted",
                        "level": "success"
                }
-       ]}
+       ],
+               "response": {
+               "id": 9,
+               "name": "quest",
+               "latitude": 0,
+               "longitude": 0,
+               "lastUpdated": "2018-11-15T17:54:30.596321Z"
+       }}
diff --git a/lib/go-tc/coordinates.go b/lib/go-tc/coordinates.go
index 0c86b67eb4..be39d2618e 100644
--- a/lib/go-tc/coordinates.go
+++ b/lib/go-tc/coordinates.go
@@ -19,7 +19,11 @@ package tc
  * under the License.
  */
 
+import "time"
+
 // CoordinatesResponse is a list of Coordinates as a response.
+// Deprecated: In newer API versions, coordinates are represented by the
+// CoordinatesResponseV5 structures.
 // swagger:response CoordinatesResponse
 // in: body
 type CoordinatesResponse struct {
@@ -30,6 +34,8 @@ type CoordinatesResponse struct {
 
 // CoordinateResponse is a single Coordinate response for Update and Create to
 // depict what changed.
+// Deprecated: In newer API versions, coordinates are represented by the
+// CoordinateResponseV5 structures.
 // swagger:response CoordinateResponse
 // in: body
 type CoordinateResponse struct {
@@ -40,6 +46,8 @@ type CoordinateResponse struct {
 
 // Coordinate is a representation of a Coordinate as it relates to the Traffic
 // Ops data model.
+// Deprecated: In newer API versions, coordinates are represented by the
+// CoordinateV5 structures.
 type Coordinate struct {
 
        // The Coordinate to retrieve
@@ -71,6 +79,8 @@ type Coordinate struct {
 
 // CoordinateNullable is identical to Coordinate except that its fields are
 // reference values, which allows them to be nil.
+// Deprecated: In newer API versions, coordinates are represented by the
+// CoordinateV5 structures.
 type CoordinateNullable struct {
 
        // The Coordinate to retrieve
@@ -99,3 +109,35 @@ type CoordinateNullable struct {
        //
        LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"`
 }
+
+// CoordinateV5 is the representation of a Coordinate used in the latest minor
+// version of APIv5.
+type CoordinateV5 = CoordinateV50
+
+// CoordinateV50 is the representation of a Coordinate used in API v5.0.
+type CoordinateV50 struct {
+       // The integral, unique identifier of a Coordinate.
+       ID *int `json:"id" db:"id"`
+       // The Coordinate's name.
+       Name string `json:"name" db:"name"`
+       // The latitude of the Coordinate.
+       Latitude float64 `json:"latitude" db:"latitude"`
+       // The longitude of the Coordinate.
+       Longitude float64 `json:"longitude" db:"longitude"`
+       // The time and date at which the Coordinate was last modified.
+       LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+}
+
+// CoordinateResponseV5 is the type of a response from the /coordinates 
endpoint
+// in the latest minor version of APIv5.
+type CoordinateResponseV5 struct {
+       Alerts
+       Response CoordinateV5
+}
+
+// CoordinatesResponseV5 is the type of a response from the /coordinates
+// endpoint in the latest minor version of APIv5.
+type CoordinatesResponseV5 struct {
+       Alerts
+       Response []CoordinateV5
+}
diff --git a/traffic_ops/testing/api/v5/coordinates_test.go 
b/traffic_ops/testing/api/v5/coordinates_test.go
index 2a02a0c428..a73696d5b9 100644
--- a/traffic_ops/testing/api/v5/coordinates_test.go
+++ b/traffic_ops/testing/api/v5/coordinates_test.go
@@ -38,7 +38,7 @@ func TestCoordinates(t *testing.T) {
                currentTimeRFC := currentTime.Format(time.RFC1123)
                tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
 
-               methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.Coordinate]{
+               methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.CoordinateV5]{
                        "GET": {
                                "NOT MODIFIED when NO CHANGES made": {
                                        ClientSession: TOSession,
@@ -109,7 +109,7 @@ func TestCoordinates(t *testing.T) {
                        "POST": {
                                "BAD REQUEST when INVALID NAME": {
                                        ClientSession: TOSession,
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  1.1,
                                                Longitude: 2.2,
                                                Name:      "",
@@ -118,7 +118,7 @@ func TestCoordinates(t *testing.T) {
                                },
                                "BAD REQUEST when INVALID LATITUDE": {
                                        ClientSession: TOSession,
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  20000,
                                                Longitude: 2.2,
                                                Name:      "testlatitude",
@@ -127,7 +127,7 @@ func TestCoordinates(t *testing.T) {
                                },
                                "BAD REQUEST when INVALID LONGITUDE": {
                                        ClientSession: TOSession,
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  1.1,
                                                Longitude: 20000,
                                                Name:      "testlongitude",
@@ -139,7 +139,7 @@ func TestCoordinates(t *testing.T) {
                                "OK when VALID request": {
                                        EndpointID:    GetCoordinateID(t, 
"coordinate2"),
                                        ClientSession: TOSession,
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  7.7,
                                                Longitude: 8.8,
                                                Name:      "coordinate2",
@@ -149,7 +149,7 @@ func TestCoordinates(t *testing.T) {
                                },
                                "NOT FOUND when INVALID ID parameter": {
                                        EndpointID: func() int { return 111111 
},
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  1.1,
                                                Longitude: 2.2,
                                                Name:      "coordinate1",
@@ -161,7 +161,7 @@ func TestCoordinates(t *testing.T) {
                                        EndpointID:    GetCoordinateID(t, 
"coordinate1"),
                                        ClientSession: TOSession,
                                        RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: 
{currentTimeRFC}}},
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  1.1,
                                                Longitude: 2.2,
                                                Name:      "coordinate1",
@@ -171,7 +171,7 @@ func TestCoordinates(t *testing.T) {
                                "PRECONDITION FAILED when updating with IFMATCH 
ETAG Header": {
                                        EndpointID:    GetCoordinateID(t, 
"coordinate1"),
                                        ClientSession: TOSession,
-                                       RequestBody: tc.Coordinate{
+                                       RequestBody: tc.CoordinateV5{
                                                Latitude:  1.1,
                                                Longitude: 2.2,
                                                Name:      "coordinate1",
@@ -202,23 +202,23 @@ func TestCoordinates(t *testing.T) {
                                                })
                                        case "POST":
                                                t.Run(name, func(t *testing.T) {
-                                                       alerts, reqInf, err := 
testCase.ClientSession.CreateCoordinate(testCase.RequestBody, 
testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.CreateCoordinate(testCase.RequestBody, 
testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
+                                                               check(t, 
reqInf, nil, resp.Alerts, err)
                                                        }
                                                })
                                        case "PUT":
                                                t.Run(name, func(t *testing.T) {
-                                                       alerts, reqInf, err := 
testCase.ClientSession.UpdateCoordinate(testCase.EndpointID(), 
testCase.RequestBody, testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateCoordinate(testCase.EndpointID(), 
testCase.RequestBody, testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
+                                                               check(t, 
reqInf, nil, resp.Alerts, err)
                                                        }
                                                })
                                        case "DELETE":
                                                t.Run(name, func(t *testing.T) {
-                                                       alerts, reqInf, err := 
testCase.ClientSession.DeleteCoordinate(testCase.EndpointID(), 
testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.DeleteCoordinate(testCase.EndpointID(), 
testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
+                                                               check(t, 
reqInf, nil, resp.Alerts, err)
                                                        }
                                                })
                                        }
@@ -231,7 +231,7 @@ func TestCoordinates(t *testing.T) {
 func validateCoordinateFields(expectedResp map[string]interface{}) 
utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
                assert.RequireNotNil(t, resp, "Expected Coordinate response to 
not be nil.")
-               coordinateResp := resp.([]tc.Coordinate)
+               coordinateResp := resp.([]tc.CoordinateV5)
                for field, expected := range expectedResp {
                        for _, coordinate := range coordinateResp {
                                switch field {
@@ -262,7 +262,7 @@ func validateCoordinateUpdateCreateFields(name string, 
expectedResp map[string]i
 
 func validateCoordinatePagination(paginationParam string) utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
-               paginationResp := resp.([]tc.Coordinate)
+               paginationResp := resp.([]tc.CoordinateV5)
                opts := client.NewRequestOptions()
                opts.QueryParameters.Set("orderby", "id")
                respBase, _, err := TOSession.GetCoordinates(opts)
@@ -285,7 +285,7 @@ func validateCoordinateSort() utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, 
alerts tc.Alerts, _ error) {
                assert.RequireNotNil(t, resp, "Expected Coordinate response to 
not be nil.")
                var coordinateNames []string
-               coordinateResp := resp.([]tc.Coordinate)
+               coordinateResp := resp.([]tc.CoordinateV5)
                for _, coordinate := range coordinateResp {
                        coordinateNames = append(coordinateNames, 
coordinate.Name)
                }
@@ -296,7 +296,7 @@ func validateCoordinateSort() utils.CkReqFunc {
 func validateCoordinateDescSort() utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, 
alerts tc.Alerts, _ error) {
                assert.RequireNotNil(t, resp, "Expected Coordinate response to 
not be nil.")
-               coordinateDescResp := resp.([]tc.Coordinate)
+               coordinateDescResp := resp.([]tc.CoordinateV5)
                var descSortedList []string
                var ascSortedList []string
                assert.RequireGreaterOrEqual(t, len(coordinateDescResp), 2, 
"Need at least 2 Coordinates in Traffic Ops to test desc sort, found: %d", 
len(coordinateDescResp))
@@ -324,7 +324,9 @@ func GetCoordinateID(t *testing.T, coordinateName string) 
func() int {
                coordinatesResp, _, err := TOSession.GetCoordinates(opts)
                assert.RequireNoError(t, err, "Get Coordinate Request failed 
with error:", err)
                assert.RequireEqual(t, 1, len(coordinatesResp.Response), 
"Expected response object length 1, but got %d", len(coordinatesResp.Response))
-               return coordinatesResp.Response[0].ID
+               id := coordinatesResp.Response[0].ID
+               assert.RequireNotNil(t, id, "Traffic Ops responded with nil 
Coordinate ID")
+               return *id
        }
 }
 
@@ -339,11 +341,14 @@ func DeleteTestCoordinates(t *testing.T) {
        coordinates, _, err := TOSession.GetCoordinates(client.RequestOptions{})
        assert.NoError(t, err, "Cannot get Coordinates: %v - alerts: %+v", err, 
coordinates.Alerts)
        for _, coordinate := range coordinates.Response {
-               alerts, _, err := TOSession.DeleteCoordinate(coordinate.ID, 
client.RequestOptions{})
+               id := coordinate.ID
+               assert.RequireNotNil(t, id, "Traffic Ops responded with nil 
Coordinate ID")
+
+               alerts, _, err := TOSession.DeleteCoordinate(*id, 
client.RequestOptions{})
                assert.NoError(t, err, "Unexpected error deleting Coordinate 
'%s' (#%d): %v - alerts: %+v", coordinate.Name, coordinate.ID, err, 
alerts.Alerts)
                // Retrieve the Coordinate to see if it got deleted
                opts := client.NewRequestOptions()
-               opts.QueryParameters.Set("id", strconv.Itoa(coordinate.ID))
+               opts.QueryParameters.Set("id", strconv.Itoa(*id))
                getCoordinate, _, err := TOSession.GetCoordinates(opts)
                assert.NoError(t, err, "Error getting Coordinate '%s' after 
deletion: %v - alerts: %+v", coordinate.Name, err, getCoordinate.Alerts)
                assert.Equal(t, 0, len(getCoordinate.Response), "Expected 
Coordinate '%s' to be deleted, but it was found in Traffic Ops", 
coordinate.Name)
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go 
b/traffic_ops/testing/api/v5/traffic_control_test.go
index bd18b2b446..5fd0eea97f 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -26,7 +26,7 @@ type TrafficControl struct {
        CDNLocks                                          []tc.CDNLock          
                  `json:"cdnlocks"`
        CacheGroups                                       
[]tc.CacheGroupNullable                 `json:"cachegroups"`
        Capabilities                                      []tc.Capability       
                  `json:"capability"`
-       Coordinates                                       []tc.Coordinate       
                  `json:"coordinates"`
+       Coordinates                                       []tc.CoordinateV5     
                  `json:"coordinates"`
        DeliveryServicesRegexes                           
[]tc.DeliveryServiceRegexesTest         `json:"deliveryServicesRegexes"`
        DeliveryServiceRequests                           
[]tc.DeliveryServiceRequestV5           `json:"deliveryServiceRequests"`
        DeliveryServiceRequestComments                    
[]tc.DeliveryServiceRequestComment      `json:"deliveryServiceRequestComments"`
diff --git a/traffic_ops/traffic_ops_golang/api/api.go 
b/traffic_ops/traffic_ops_golang/api/api.go
index 52c1674434..f8b95f5e65 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -625,6 +625,15 @@ func (inf APIInfo) UseIMS() bool {
        return inf.Config.UseIMS && inf.request.Header.Get(rfc.IfModifiedSince) 
!= ""
 }
 
+// WriteNotModifiedResponse writes a 304 Not Modified response with the given
+// last modification time to the provided response writer. The request must be
+// provided as well, so that it can be marked as handled.
+func WriteNotModifiedResponse(t time.Time, w http.ResponseWriter, r 
*http.Request) {
+       AddLastModifiedHdr(w, t)
+       w.WriteHeader(http.StatusNotModified)
+       WriteResp(w, r, nil)
+}
+
 // CheckPrecondition checks a request's "preconditions" - its If-Match and
 // If-Unmodified-Since headers versus the last updated time of the requested
 // object(s), and returns (in order), an HTTP response code appropriate for the
@@ -794,11 +803,20 @@ func (val APIInfoImpl) APIInfo() *APIInfo {
        return val.ReqInfo
 }
 
+// Version represents an API version.
 type Version struct {
        Major uint64
        Minor uint64
 }
 
+// String implements the fmt.Stringer interface.
+func (v *Version) String() string {
+       if v == nil {
+               return "{{null}}"
+       }
+       return strconv.FormatUint(v.Major, 10) + "." + 
strconv.FormatUint(v.Minor, 10)
+}
+
 func (v *Version) LessThan(otherVersion *Version) bool {
        return v.Major < otherVersion.Major || (v.Major == otherVersion.Major 
&& v.Minor < otherVersion.Minor)
 }
diff --git a/traffic_ops/traffic_ops_golang/api/api_test.go 
b/traffic_ops/traffic_ops_golang/api/api_test.go
index 2a08ee8e28..3118e6d6b5 100644
--- a/traffic_ops/traffic_ops_golang/api/api_test.go
+++ b/traffic_ops/traffic_ops_golang/api/api_test.go
@@ -24,6 +24,7 @@ import (
        "database/sql"
        "encoding/json"
        "errors"
+       "fmt"
        "net/http"
        "net/url"
        "testing"
@@ -33,6 +34,17 @@ import (
        "github.com/apache/trafficcontrol/lib/go-tc"
 )
 
+func ExampleVersion_String() {
+       // Because api.Info objects use pointers to Versions, this handles nil
+       // without needing the caller to do it - because that's annoying.
+       var v *Version
+       fmt.Println(v)
+       v = &Version{Major: 4, Minor: 20}
+       fmt.Println(v.String())
+       // Output: {{null}}
+       // 4.20
+}
+
 func TestCamelCase(t *testing.T) {
        testStrings := []string{"hello_world", "trailing_underscore_", 
"w_h_a_t____"}
        expected := []string{"helloWorld", "trailingUnderscore", "wHAT"}
diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go 
b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
index 5990fad907..06078b5596 100644
--- a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
+++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
@@ -1,3 +1,5 @@
+// Package coordinate contains API handlers and associated logic for servicing
+// the `/coordinates` API endpoint.
 package coordinate
 
 /*
@@ -20,48 +22,92 @@ package coordinate
  */
 
 import (
+       "database/sql"
+       "encoding/json"
+       "errors"
+       "fmt"
        "net/http"
        "strconv"
        "strings"
        "time"
 
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
        "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
 
        validation "github.com/go-ozzo/ozzo-validation"
 )
 
-// we need a type alias to define functions on
+// TOCoordinate is a "CRUDer"-based API wrapper for Coordinate objects.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 type TOCoordinate struct {
        api.APIInfoImpl `json:"-"`
        tc.CoordinateNullable
 }
 
-func (v *TOCoordinate) SetLastUpdated(t tc.TimeNoMod) { v.LastUpdated = &t }
-func (v *TOCoordinate) InsertQuery() string           { return insertQuery() }
-func (v *TOCoordinate) NewReadObj() interface{}       { return 
&tc.CoordinateNullable{} }
-func (v *TOCoordinate) SelectQuery() string           { return selectQuery() }
-func (v *TOCoordinate) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
+// SetLastUpdated implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (coordinate *TOCoordinate) SetLastUpdated(t tc.TimeNoMod) { 
coordinate.LastUpdated = &t }
+
+// InsertQuery implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) InsertQuery() string { return insertQuery() }
+
+// NewReadObj implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) NewReadObj() interface{} { return 
&tc.CoordinateNullable{} }
+
+// SelectQuery implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) SelectQuery() string { return selectQuery() }
+
+// ParamColumns implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
        return map[string]dbhelpers.WhereColumnInfo{
-               "id":   dbhelpers.WhereColumnInfo{Column: "id", Checker: 
api.IsInt},
-               "name": dbhelpers.WhereColumnInfo{Column: "name"},
+               "id":   {Column: "id", Checker: api.IsInt},
+               "name": {Column: "name"},
        }
 }
 
-func (v *TOCoordinate) GetLastUpdated() (*time.Time, bool, error) {
-       return api.GetLastUpdated(v.APIInfo().Tx, *v.ID, "coordinate")
+// GetLastUpdated implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (coordinate *TOCoordinate) GetLastUpdated() (*time.Time, bool, error) {
+       return api.GetLastUpdated(coordinate.APIInfo().Tx, *coordinate.ID, 
"coordinate")
 }
 
-func (v *TOCoordinate) UpdateQuery() string { return updateQuery() }
-func (v *TOCoordinate) DeleteQuery() string { return deleteQuery() }
+// UpdateQuery implements a "CRUD"er interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) UpdateQuery() string { return updateQuery() }
+
+// DeleteQuery implements a "CRUD"er interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) DeleteQuery() string { return deleteQuery() }
+
+// GetKeyFieldsInfo implements a "CRUD"er interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func (coordinate TOCoordinate) GetKeyFieldsInfo() []api.KeyFieldInfo {
        return []api.KeyFieldInfo{{Field: "id", Func: api.GetIntKey}}
 }
 
-// Implementation of the Identifier, Validator interface functions
+// GetKeys implements the Identifier and Validator interfaces.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func (coordinate TOCoordinate) GetKeys() (map[string]interface{}, bool) {
        if coordinate.ID == nil {
                return map[string]interface{}{"id": 0}, false
@@ -69,6 +115,9 @@ func (coordinate TOCoordinate) GetKeys() 
(map[string]interface{}, bool) {
        return map[string]interface{}{"id": *coordinate.ID}, true
 }
 
+// GetAuditName implements a "CRUD"er interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func (coordinate TOCoordinate) GetAuditName() string {
        if coordinate.Name != nil {
                return *coordinate.Name
@@ -79,12 +128,18 @@ func (coordinate TOCoordinate) GetAuditName() string {
        return "0"
 }
 
+// GetType implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func (coordinate TOCoordinate) GetType() string {
        return "coordinate"
 }
 
+// SetKeys implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func (coordinate *TOCoordinate) SetKeys(keys map[string]interface{}) {
-       i, _ := keys["id"].(int) //this utilizes the non panicking type 
assertion, if the thrown away ok variable is false i will be the zero of the 
type, 0 here.
+       i, _ := keys["id"].(int)
        coordinate.ID = &i
 }
 
@@ -104,13 +159,18 @@ func isValidCoordinateChar(r rune) bool {
        return false
 }
 
-// IsValidCoordinateName returns true if the name contains only characters 
valid for a Coordinate name
+// IsValidCoordinateName returns true if the name contains only characters 
valid
+// for a Coordinate name.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
 func IsValidCoordinateName(str string) bool {
        i := strings.IndexFunc(str, func(r rune) bool { return 
!isValidCoordinateChar(r) })
        return i == -1
 }
 
 // Validate fulfills the api.Validator interface.
+// Deprecated: All future Coordinate versions should use non-"CRUDer"
+// validation.
 func (coordinate TOCoordinate) Validate() (error, error) {
        validName := validation.NewStringRule(IsValidCoordinateName, "invalid 
characters found - Use alphanumeric . or - or _ .")
        latitudeErr := "Must be a floating point number within the range +-90"
@@ -123,35 +183,77 @@ func (coordinate TOCoordinate) Validate() (error, error) {
        return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
+// Create implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer" 
Create
+// function.
 func (coord *TOCoordinate) Create() (error, error, int) { return 
api.GenericCreate(coord) }
+
+// Read implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer" Read
+// function.
 func (coord *TOCoordinate) Read(h http.Header, useIMS bool) ([]interface{}, 
error, error, int, *time.Time) {
        api.DefaultSort(coord.APIInfo(), "name")
        return api.GenericRead(h, coord, useIMS)
 }
-func (v *TOCoordinate) SelectMaxLastUpdatedQuery(where, orderBy, pagination, 
tableName string) string {
-       return `SELECT max(t) from (
-               SELECT max(last_updated) as t from ` + tableName + ` c ` + 
where + orderBy + pagination +
-               ` UNION ALL
-       select max(last_updated) as t from last_deleted l where l.table_name='` 
+ tableName + `') as res`
+
+func selectMaxLastUpdatedQuery(where, orderBy, pagination string) string {
+       return `
+SELECT max(t) FROM (
+       SELECT max(last_updated) AS t
+       FROM (
+               SELECT *
+               FROM coordinate c
+               ` + where + orderBy + pagination +
+               `       ) AS coords
+       UNION ALL
+       SELECT max(last_updated) AS t
+       FROM last_deleted l
+       WHERE l.table_name='coordinate'
+) AS res`
+}
+
+// SelectMaxLastUpdatedQuery implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (*TOCoordinate) SelectMaxLastUpdatedQuery(where, orderBy, pagination, _ 
string) string {
+       return selectMaxLastUpdatedQuery(where, orderBy, pagination)
 }
 
+// Update implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer" 
Update
+// function.
 func (coord *TOCoordinate) Update(h http.Header) (error, error, int) {
        return api.GenericUpdate(h, coord)
 }
-func (coord *TOCoordinate) Delete() (error, error, int) { return 
api.GenericDelete(coord) }
 
-func selectQuery() string {
-       query := `SELECT
-id,
-latitude,
-longitude,
-last_updated,
-name
+// Delete implements a "CRUDer" interface.
+// Deprecated: All future Coordinate versions should use the non-"CRUDer"
+// methodology.
+func (coord *TOCoordinate) Delete() (error, error, int) { return 
api.GenericDelete(coord) }
 
+const readQuery = `
+SELECT
+       id,
+       latitude,
+       longitude,
+       last_updated,
+       name
 FROM coordinate c`
-       return query
+
+func selectQuery() string {
+       return readQuery
 }
 
+const putQuery = `
+UPDATE coordinate
+SET
+       latitude=$1,
+       longitude=$2,
+       name=$3
+WHERE id=$4
+RETURNING
+       last_updated`
+
 func updateQuery() string {
        query := `UPDATE
 coordinate SET
@@ -162,6 +264,19 @@ WHERE id=:id RETURNING last_updated`
        return query
 }
 
+const createQuery = `
+INSERT INTO coordinate (
+       latitude,
+       longitude,
+       name
+) VALUES (
+       $1,
+       $2,
+       $3
+) RETURNING
+       id,
+       last_updated`
+
 func insertQuery() string {
        query := `INSERT INTO coordinate (
 latitude,
@@ -173,6 +288,219 @@ name) VALUES (
        return query
 }
 
+const delQuery = `
+DELETE FROM coordinate
+WHERE id = $1
+RETURNING
+       latitude,
+       longitude,
+       name,
+       last_updated
+`
+
 func deleteQuery() string {
        return `DELETE FROM coordinate WHERE id = :id`
 }
+
+// Read is the handler for GET requests made to the /coordinates API (in APIv5
+// and later).
+func Read(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       cols := map[string]dbhelpers.WhereColumnInfo{
+               "id":   {Column: "c.id", Checker: api.IsInt},
+               "name": {Column: "c.name", Checker: nil},
+       }
+       api.DefaultSort(inf, "name")
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols)
+       if len(errs) > 0 {
+               errCode = http.StatusBadRequest
+               userErr = util.JoinErrs(errs)
+               api.HandleErr(w, r, tx, errCode, userErr, nil)
+               return
+       }
+
+       var maxTime time.Time
+       if inf.UseIMS() {
+               var runSecond bool
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, 
r.Header, queryValues, selectMaxLastUpdatedQuery(where, orderBy, pagination))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       api.WriteNotModifiedResponse(maxTime, w, r)
+                       return
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       query := readQuery + where + orderBy + pagination
+       rows, err := inf.Tx.NamedQuery(query, queryValues)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("querying coordinates: %w", err))
+               return
+       }
+       defer log.Close(rows, "closing coordinate query rows")
+
+       cs := []tc.CoordinateV5{}
+       for rows.Next() {
+               var c tc.CoordinateV5
+               err := rows.Scan(&c.ID, &c.Latitude, &c.Longitude, 
&c.LastUpdated, &c.Name)
+               if err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, fmt.Errorf("scanning a coordinate: %w", err))
+                       return
+               }
+               cs = append(cs, c)
+       }
+
+       api.WriteResp(w, r, cs)
+}
+
+// isValid returns an error describing why c isn't a valid Coordinate, or nil 
if
+// it's actually valid.
+func isValid(c tc.CoordinateV5) error {
+       validName := validation.NewStringRule(IsValidCoordinateName, "invalid 
characters found - Use alphanumeric . or - or _ .")
+       latitudeErr := "Must be a floating point number within the range +-90"
+       longitudeErr := "Must be a floating point number within the range +-180"
+       errs := validation.Errors{
+               "name":      validation.Validate(c.Name, validation.Required, 
validName),
+               "latitude":  validation.Validate(c.Latitude, 
validation.Min(-90.0).Error(latitudeErr), 
validation.Max(90.0).Error(latitudeErr)),
+               "longitude": validation.Validate(c.Longitude, 
validation.Min(-180.0).Error(longitudeErr), 
validation.Max(180.0).Error(longitudeErr)),
+       }
+       return util.JoinErrs(tovalidate.ToErrors(errs))
+}
+
+// Create is the handler for POST requests made to the /coordinates API (in
+// APIv5 and later).
+func Create(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       var c tc.CoordinateV5
+       err := json.NewDecoder(r.Body).Decode(&c)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       if err = isValid(c); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       if err = tx.QueryRow(createQuery, c.Latitude, c.Longitude, 
c.Name).Scan(&c.ID, &c.LastUpdated); err != nil {
+               userErr, sysErr, errCode = api.ParseDBError(err)
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       w.Header().Set(rfc.Location, fmt.Sprintf("/api/%s/coordinates?id=%d", 
inf.Version, *c.ID))
+       w.WriteHeader(http.StatusCreated)
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, fmt.Sprintf("Coordinate 
'%s' (#%d) created", c.Name, *c.ID), c)
+
+       changeLogMsg := fmt.Sprintf("USER: %s, COORDINATE: %s (#%d), ACTION: 
%s", inf.User.UserName, c.Name, *c.ID, api.Created)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
+
+// Update is the handler for PUT requests made to the /coordinates API (in API
+// v5 and later).
+func Update(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, 
[]string{"id"})
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       var c tc.CoordinateV5
+       err := json.NewDecoder(r.Body).Decode(&c)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       id := inf.IntParams["id"]
+       if c.ID != nil {
+               if *c.ID != id {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("ID mismatch; URI specifies %d but payload is for Coordinate #%d", 
id, *c.ID), nil)
+                       return
+               }
+       } else {
+               c.ID = util.Ptr(id)
+       }
+
+       userErr, sysErr, statusCode := api.CheckIfUnModified(r.Header, inf.Tx, 
id, "coordinate")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+               return
+       }
+
+       if err = isValid(c); err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+               return
+       }
+
+       if err = tx.QueryRow(putQuery, c.Latitude, c.Longitude, c.Name, 
id).Scan(&c.LastUpdated); err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       userErr = fmt.Errorf("no such Coordinate: #%d", id)
+                       errCode = http.StatusNotFound
+                       sysErr = nil
+               } else {
+                       userErr, sysErr, errCode = api.ParseDBError(err)
+               }
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, fmt.Sprintf("Coordinate 
'%s' (#%d) updated", c.Name, id), c)
+
+       changeLogMsg := fmt.Sprintf("USER: %s, COORDINATE: %s (#%d), ACTION: 
%s", inf.User.UserName, c.Name, id, api.Updated)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
+
+// Delete is the handler for PUT requests made to the /coordinates API (in API
+// v5 and later).
+func Delete(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, 
[]string{"id"})
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       id := inf.IntParams["id"]
+
+       c := tc.CoordinateV5{
+               ID: util.Ptr(id),
+       }
+       if err := tx.QueryRow(delQuery, id).Scan(&c.Latitude, &c.Longitude, 
&c.Name, &c.LastUpdated); err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       userErr = fmt.Errorf("no such Coordinate: #%d", id)
+                       errCode = http.StatusNotFound
+                       sysErr = nil
+               } else {
+                       userErr, sysErr, errCode = api.ParseDBError(err)
+               }
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, fmt.Sprintf("Coordinate 
'%s' (#%d) deleted", c.Name, id), c)
+
+       changeLogMsg := fmt.Sprintf("USER: %s, COORDINATE: %s (#%d), ACTION: 
%s", inf.User.UserName, c.Name, id, api.Deleted)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+}
diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go 
b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
index 0fa8bb5971..1a468a13af 100644
--- a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
+++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
@@ -100,16 +100,18 @@ func TestReadCoordinates(t *testing.T) {
 }
 
 func TestFuncs(t *testing.T) {
-       if strings.Index(selectQuery(), "SELECT") != 0 {
+       trim := func(s string) string { return strings.TrimSpace((s)) }
+
+       if !strings.HasPrefix(trim(selectQuery()), "SELECT") {
                t.Errorf("expected selectQuery to start with SELECT")
        }
-       if strings.Index(insertQuery(), "INSERT") != 0 {
+       if !strings.HasPrefix(trim(insertQuery()), "INSERT") {
                t.Errorf("expected insertQuery to start with INSERT")
        }
-       if strings.Index(updateQuery(), "UPDATE") != 0 {
+       if !strings.HasPrefix(trim(updateQuery()), "UPDATE") {
                t.Errorf("expected updateQuery to start with UPDATE")
        }
-       if strings.Index(deleteQuery(), "DELETE") != 0 {
+       if !strings.HasPrefix(trim(deleteQuery()), "DELETE") {
                t.Errorf("expected deleteQuery to start with DELETE")
        }
 }
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 8a993a4752..618002e4a6 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -356,10 +356,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `about/?$`, Handler: about.Handler(), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, 
Middlewares: nil, ID: 431750116631},
 
                //Coordinates
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `coordinates/?$`, Handler: 
api.ReadHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"COORDINATE:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 49670074531},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `coordinates/?$`, Handler: 
api.UpdateHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:UPDATE", 
"COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
46892617431},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `coordinates/?$`, Handler: 
api.CreateHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:CREATE", 
"COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
442811215731},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `coordinates/?$`, Handler: 
api.DeleteHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:DELETE", 
"COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
430384988931},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `coordinates/?$`, Handler: coordinate.Read, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, 
ID: 49670074531},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `coordinates/?$`, Handler: coordinate.Update, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"COORDINATE:UPDATE", "COORDINATE:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 46892617431},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `coordinates/?$`, Handler: coordinate.Create, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"COORDINATE:CREATE", "COORDINATE:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 442811215731},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `coordinates/?$`, Handler: coordinate.Delete, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"COORDINATE:DELETE", "COORDINATE:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 430384988931},
 
                //CDN notification
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `cdn_notifications/?$`, Handler: cdnnotification.Read, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
22212245141},
diff --git a/traffic_ops/v5-client/coordinate.go 
b/traffic_ops/v5-client/coordinate.go
index 9397938b69..f5c9346ea7 100644
--- a/traffic_ops/v5-client/coordinate.go
+++ b/traffic_ops/v5-client/coordinate.go
@@ -27,38 +27,38 @@ import (
 const apiCoordinates = "/coordinates"
 
 // CreateCoordinate creates the given Coordinate.
-func (to *Session) CreateCoordinate(coordinate tc.Coordinate, opts 
RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
-       var alerts tc.Alerts
-       reqInf, err := to.post(apiCoordinates, opts, coordinate, &alerts)
-       return alerts, reqInf, err
+func (to *Session) CreateCoordinate(coordinate tc.CoordinateV5, opts 
RequestOptions) (tc.CoordinateResponseV5, toclientlib.ReqInf, error) {
+       var resp tc.CoordinateResponseV5
+       reqInf, err := to.post(apiCoordinates, opts, coordinate, &resp)
+       return resp, reqInf, err
 }
 
 // UpdateCoordinate replaces the Coordinate with the given ID with the one
 // provided.
-func (to *Session) UpdateCoordinate(id int, coordinate tc.Coordinate, opts 
RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
+func (to *Session) UpdateCoordinate(id int, coordinate tc.CoordinateV5, opts 
RequestOptions) (tc.CoordinateResponseV5, toclientlib.ReqInf, error) {
        if opts.QueryParameters == nil {
                opts.QueryParameters = url.Values{}
        }
        opts.QueryParameters.Set("id", strconv.Itoa(id))
-       var alerts tc.Alerts
-       reqInf, err := to.put(apiCoordinates, opts, coordinate, &alerts)
-       return alerts, reqInf, err
+       var resp tc.CoordinateResponseV5
+       reqInf, err := to.put(apiCoordinates, opts, coordinate, &resp)
+       return resp, reqInf, err
 }
 
 // GetCoordinates returns all Coordinates in Traffic Ops.
-func (to *Session) GetCoordinates(opts RequestOptions) 
(tc.CoordinatesResponse, toclientlib.ReqInf, error) {
-       var data tc.CoordinatesResponse
+func (to *Session) GetCoordinates(opts RequestOptions) 
(tc.CoordinatesResponseV5, toclientlib.ReqInf, error) {
+       var data tc.CoordinatesResponseV5
        reqInf, err := to.get(apiCoordinates, opts, &data)
        return data, reqInf, err
 }
 
 // DeleteCoordinate deletes the Coordinate with the given ID.
-func (to *Session) DeleteCoordinate(id int, opts RequestOptions) (tc.Alerts, 
toclientlib.ReqInf, error) {
+func (to *Session) DeleteCoordinate(id int, opts RequestOptions) 
(tc.CoordinateResponseV5, toclientlib.ReqInf, error) {
        if opts.QueryParameters == nil {
                opts.QueryParameters = url.Values{}
        }
        opts.QueryParameters.Set("id", strconv.Itoa(id))
-       var alerts tc.Alerts
-       reqInf, err := to.del(apiCoordinates, opts, &alerts)
-       return alerts, reqInf, err
+       var resp tc.CoordinateResponseV5
+       reqInf, err := to.del(apiCoordinates, opts, &resp)
+       return resp, reqInf, err
 }
diff --git a/traffic_ops/v5-client/origin.go b/traffic_ops/v5-client/origin.go
index b735527780..c5f050ed88 100644
--- a/traffic_ops/v5-client/origin.go
+++ b/traffic_ops/v5-client/origin.go
@@ -82,7 +82,7 @@ func (to *Session) originIDs(origin *tc.Origin) error {
                if len(coordinates.Response) == 0 {
                        return fmt.Errorf("no coordinate with name '%s'", 
*origin.Coordinate)
                }
-               origin.CoordinateID = &coordinates.Response[0].ID
+               origin.CoordinateID = coordinates.Response[0].ID
        }
 
        if origin.TenantID == nil && origin.Tenant != nil {


Reply via email to