This is an automated email from the ASF dual-hosted git repository.
srijeet0406 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 068d30f4a7 Fixes Divisions V5 apis to respond with RFC3339 date/time
Format (#7612)
068d30f4a7 is described below
commit 068d30f4a734f99d23ba2911d4c8735534bc995d
Author: Jagan Parthiban <[email protected]>
AuthorDate: Thu Jul 13 21:08:09 2023 +0530
Fixes Divisions V5 apis to respond with RFC3339 date/time Format (#7612)
* RFC3339 for Divisions V5 API
* Implemented IfModifiedSince and IfUnModifiedSince
* Updated Integration Tests for Divisions V5 APIs
* Updated Docs for divisions v5 rfc3339 changes.
* Added Unit test for DivisionExists func
* Updated CHANGELOG.md
* Updated Error Message
---
CHANGELOG.md | 1 +
docs/source/api/v5/divisions.rst | 19 +-
docs/source/api/v5/divisions_id.rst | 25 ++-
lib/go-tc/divisions.go | 30 +++
traffic_ops/testing/api/v5/divisions_test.go | 16 +-
traffic_ops/testing/api/v5/traffic_control_test.go | 2 +-
.../traffic_ops_golang/dbhelpers/db_helpers.go | 15 ++
.../dbhelpers/db_helpers_test.go | 51 +++++
.../traffic_ops_golang/division/divisions.go | 241 +++++++++++++++++++++
traffic_ops/traffic_ops_golang/routing/routes.go | 8 +-
traffic_ops/v5-client/division.go | 8 +-
11 files changed, 382 insertions(+), 34 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3da5a61a84..01271dd0d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -68,6 +68,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#4393](https://github.com/apache/trafficcontrol/issues/4393) *Traffic Ops*
Fixed the error code and alert structure when TO is queried for a delivery
service with no ssl keys.
- [#7623] (https://github.com/apache/trafficcontrol/pull/7623) *Traffic Ops*
Removed TryIfModifiedSinceQuery from servicecategories.go and reused from ims.go
- [#7608](https://github.com/apache/trafficcontrol/pull/7608) *Traffic
Monitor* Use stats_over_http(plugin.system_stats.timestamp_ms) timestamp field
to calculate bandwidth for TM's caches.
+- [#7612](https://github.com/apache/trafficcontrol/pull/7612) *Traffic Ops*
Fixes Divisions V5 apis to respond with RFC3339 date/time Format
- [#6318](https://github.com/apache/trafficcontrol/issues/6318) *Docs*
Included docs for POST, PUT, DELETE (v3,v4,v5) for statuses and statuses{id}
- [#7561](https://github.com/apache/trafficcontrol/pull/7561) *Traffic Ops*
*Traffic Ops* Fixed `ASN` V5 apis to respond with `RFC3339` date/time Format.
- [#7598](https://github.com/apache/trafficcontrol/pull/7598) *Traffic Ops*
Fixes Server Capability V5 Type Name Minor version
diff --git a/docs/source/api/v5/divisions.rst b/docs/source/api/v5/divisions.rst
index ba6edf6188..c542bfc802 100644
--- a/docs/source/api/v5/divisions.rst
+++ b/docs/source/api/v5/divisions.rst
@@ -53,10 +53,19 @@ Request Structure
| | defined to make use of ``page``.
|
+-----------+---------------------------------------------------------------------------------------------------------------+
+.. code-block:: http
+ :caption: Request Example
+
+ GET /api/5.0/divisions HTTP/1.1
+ Host: trafficops.infra.ciab.test
+ User-Agent: curl/7.47.0
+ Accept: */*
+ Cookie: mojolicious=...
+
Response Structure
------------------
:id: An integral, unique identifier for this Division
-:lastUpdated: The date and time at which this Division was last modified, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this Division was last modified, in
:rfc:`3339`
:name: The Division name
.. code-block:: http
@@ -77,12 +86,12 @@ Response Structure
{ "response": [
{
"id": 1,
- "lastUpdated": "2018-11-29 18:38:28+00",
+ "lastUpdated": "2018-11-29T09:39:09.761097+05:30",
"name": "Quebec"
},
{
"id": 2,
- "lastUpdated": "2018-11-29 18:38:28+00",
+ "lastUpdated": "2018-11-29T15:29:31.872822+05:30",
"name": "USA"
}
]}
@@ -117,7 +126,7 @@ Request Structure
Response Structure
------------------
:id: An integral, unique identifier for this Division
-:lastUpdated: The date and time at which this Division was last modified, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this Division was last modified, in
:rfc:`3339`
:name: The Division name
.. code-block:: http
@@ -143,6 +152,6 @@ Response Structure
],
"response": {
"id": 3,
- "lastUpdated": "2018-11-29 19:52:06+00",
+ "lastUpdated": "2018-11-29T19:52:06.872822+05:30",
"name": "test"
}}
diff --git a/docs/source/api/v5/divisions_id.rst
b/docs/source/api/v5/divisions_id.rst
index b6e18cc697..e95f019091 100644
--- a/docs/source/api/v5/divisions_id.rst
+++ b/docs/source/api/v5/divisions_id.rst
@@ -57,7 +57,7 @@ Request Structure
Response Structure
------------------
:id: An integral, unique identifier for this Division
-:lastUpdated: The date and time at which this Division was last modified, in
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this Division was last modified, in
:rfc:`3339`
:name: The Division name
.. code-block:: http
@@ -83,7 +83,7 @@ Response Structure
],
"response": {
"id": 3,
- "lastUpdated": "2018-11-29 20:10:36+00",
+ "lastUpdated": "2018-11-29T16:23:53.696397+05:30",
"name": "quest"
}}
@@ -118,13 +118,9 @@ Request Structure
Content-Length: 2
Content-Type: application/json
- {}
Response Structure
------------------
-:id: An integral, unique identifier for this Division
-:lastUpdated: The date and time at which this Division was last modified, in
:ref:`non-rfc-datetime`
-:name: The Division name
.. code-block:: http
:caption: Response Example
@@ -141,9 +137,14 @@ Response Structure
Date: Thu, 29 Nov 2018 20:10:36 GMT
Content-Length: 83
- { "alerts": [
- {
- "text": "division was deleted.",
- "level": "success"
- }
- ]}
+ {
+ "alerts": [
+ {
+ "text": "division was deleted.",
+ "level": "success"
+ }
+ ],
+ "response": {
+ "id": "3"
+ }
+ }
diff --git a/lib/go-tc/divisions.go b/lib/go-tc/divisions.go
index c4d8080707..484e58bd3e 100644
--- a/lib/go-tc/divisions.go
+++ b/lib/go-tc/divisions.go
@@ -19,6 +19,8 @@ package tc
* under the License.
*/
+import "time"
+
// DivisionsResponse is a list of Divisions as a response.
// swagger:response DivisionsResponse
type DivisionsResponse struct {
@@ -60,3 +62,31 @@ type DivisionNullable struct {
LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"`
Name *string `json:"name" db:"name"`
}
+
+// DivisionsResponseV5 is an alias for the latest minor version for the major
version 5.
+type DivisionsResponseV5 DivisionsResponseV50
+
+// DivisionResponseV5 is an alias for the latest minor version for the major
version 5.
+type DivisionResponseV5 DivisionResponseV50
+
+// DivisionV5 is an alias for the latest minor version for the major version 5.
+type DivisionV5 DivisionV50
+
+// DivisionsResponseV50 is a list of Divisions as a response.
+type DivisionsResponseV50 struct {
+ Response []DivisionV5 `json:"response"`
+ Alerts
+}
+
+// DivisionResponseV50 is a single Division response for Update and Create to
+// depict what changed.
+type DivisionResponseV50 struct {
+ Response DivisionV5 `json:"response"`
+}
+
+// A DivisionV50 is a named collection of Regions.
+type DivisionV50 struct {
+ ID int `json:"id" db:"id"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ Name string `json:"name" db:"name"`
+}
diff --git a/traffic_ops/testing/api/v5/divisions_test.go
b/traffic_ops/testing/api/v5/divisions_test.go
index 8a1685310c..86f8f6f3ea 100644
--- a/traffic_ops/testing/api/v5/divisions_test.go
+++ b/traffic_ops/testing/api/v5/divisions_test.go
@@ -38,7 +38,7 @@ func TestDivisions(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.Division]{
+ methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.DivisionV5]{
"GET": {
"NOT MODIFIED when NO CHANGES made": {
ClientSession: TOSession,
@@ -110,7 +110,7 @@ func TestDivisions(t *testing.T) {
"OK when VALID request": {
EndpointID: GetDivisionID(t,
"cdn-div2"),
ClientSession: TOSession,
- RequestBody: tc.Division{
+ RequestBody: tc.DivisionV5{
Name: "testdivision",
},
Expectations:
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
@@ -120,7 +120,7 @@ func TestDivisions(t *testing.T) {
EndpointID: GetDivisionID(t,
"division1"),
ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince:
{currentTimeRFC}}},
- RequestBody: tc.Division{
+ RequestBody: tc.DivisionV5{
Name: "division1",
},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
@@ -128,7 +128,7 @@ func TestDivisions(t *testing.T) {
"PRECONDITION FAILED when updating with IFMATCH
ETAG Header": {
EndpointID: GetDivisionID(t,
"division1"),
ClientSession: TOSession,
- RequestBody: tc.Division{
+ RequestBody: tc.DivisionV5{
Name: "division1",
},
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfMatch:
{rfc.ETag(currentTime)}}},
@@ -191,7 +191,7 @@ func TestDivisions(t *testing.T) {
func validateDivisionFields(expectedResp map[string]interface{})
utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Division response to
not be nil.")
- divisionResp := resp.([]tc.Division)
+ divisionResp := resp.([]tc.DivisionV5)
for field, expected := range expectedResp {
for _, division := range divisionResp {
switch field {
@@ -218,7 +218,7 @@ func validateDivisionUpdateCreateFields(name string,
expectedResp map[string]int
func validateDivisionPagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- paginationResp := resp.([]tc.Division)
+ paginationResp := resp.([]tc.DivisionV5)
opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
respBase, _, err := TOSession.GetDivisions(opts)
@@ -241,7 +241,7 @@ func validateDivisionSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Division response to
not be nil.")
var divisionNames []string
- divisionResp := resp.([]tc.Division)
+ divisionResp := resp.([]tc.DivisionV5)
for _, division := range divisionResp {
divisionNames = append(divisionNames, division.Name)
}
@@ -252,7 +252,7 @@ func validateDivisionSort() utils.CkReqFunc {
func validateDivisionDescSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Division response to
not be nil.")
- divisionDescResp := resp.([]tc.Division)
+ divisionDescResp := resp.([]tc.DivisionV5)
var descSortedList []string
var ascSortedList []string
assert.RequireGreaterOrEqual(t, len(divisionDescResp), 2, "Need
at least 2 Divisions in Traffic Ops to test desc sort, found: %d",
len(divisionDescResp))
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go
b/traffic_ops/testing/api/v5/traffic_control_test.go
index 4279332f36..789e475021 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -34,7 +34,7 @@ type TrafficControl struct {
DeliveryServicesRequiredCapabilities
[]tc.DeliveryServicesRequiredCapability
`json:"deliveryservicesRequiredCapabilities"`
DeliveryServiceServerAssignments
[]tc.DeliveryServiceServers
`json:"deliveryServiceServerAssignments"`
TopologyBasedDeliveryServicesRequiredCapabilities
[]tc.DeliveryServicesRequiredCapability
`json:"topologyBasedDeliveryServicesRequiredCapabilities"`
- Divisions []tc.Division
`json:"divisions"`
+ Divisions []tc.DivisionV5
`json:"divisions"`
Federations []tc.CDNFederation
`json:"federations"`
FederationResolvers
[]tc.FederationResolver `json:"federation_resolvers"`
Jobs
[]tc.InvalidationJobCreateV4 `json:"jobs"`
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index e1e7d32bd8..dde6815757 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -2217,3 +2217,18 @@ func ASNExists(tx *sql.Tx, id string) (bool, error) {
}
return true, nil
}
+
+// DivisionExists confirms whether the division exists, and an error (if one
occurs).
+func DivisionExists(tx *sql.Tx, id string) (bool, error) {
+ var count int
+ if err := tx.QueryRow("SELECT count(id) FROM division AS div WHERE
div.id=$1", id).Scan(&count); err != nil {
+ return false, fmt.Errorf("error getting divisions info: %w",
err)
+ }
+ if count == 0 {
+ return false, nil
+ }
+ if count != 1 {
+ return false, fmt.Errorf("getting division info - expected row
count: 1, actual: %d", count)
+ }
+ return true, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
index 8007f1f221..0c9bda1586 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
@@ -562,3 +562,54 @@ func TestASNExists(t *testing.T) {
})
}
}
+
+func TestDivisionExists(t *testing.T) {
+ var testCases = []struct {
+ description string
+ id string
+ expectedError error
+ exists bool
+ }{
+ {
+ description: "Success: Get valid Division",
+ id: "1",
+ expectedError: nil,
+ exists: true,
+ },
+ {
+ description: "Failure: Division not in DB",
+ id: "10",
+ expectedError: sql.ErrNoRows,
+ exists: false,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.description, func(t *testing.T) {
+ mockDB, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("an error '%s' was not expected when
opening a stub database connection", err)
+ }
+ defer mockDB.Close()
+
+ db := sqlx.NewDb(mockDB, "sqlmock")
+ defer db.Close()
+
+ mock.ExpectBegin()
+ rows := sqlmock.NewRows([]string{"count"})
+ if testCase.exists {
+ rows = rows.AddRow(1)
+ }
+ mock.ExpectQuery("SELECT").WillReturnRows(rows)
+ mock.ExpectCommit()
+
+ divisionExists, err :=
DivisionExists(db.MustBegin().Tx, testCase.id)
+ if testCase.exists != divisionExists {
+ t.Errorf("Expected return exists: %t, actual
%t", testCase.exists, divisionExists)
+ }
+
+ if !errors.Is(err, testCase.expectedError) {
+ t.Errorf("DivisionExists Error. expected: %s,
actual: %s", testCase.expectedError, err)
+ }
+ })
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/division/divisions.go
b/traffic_ops/traffic_ops_golang/division/divisions.go
index 3022203242..406773ad09 100644
--- a/traffic_ops/traffic_ops_golang/division/divisions.go
+++ b/traffic_ops/traffic_ops_golang/division/divisions.go
@@ -20,16 +20,22 @@ package division
*/
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-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"
)
@@ -137,3 +143,238 @@ WHERE id=:id RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM division WHERE id=:id`
}
+
+func GetDivisions(w http.ResponseWriter, r *http.Request) {
+ var runSecond bool
+ var maxTime time.Time
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "id": {Column: "d.id", Checker: nil},
+ "name": {Column: "d.name", Checker: nil},
+ }
+ if _, ok := inf.Params["orderby"]; !ok {
+ inf.Params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ api.HandleErr(w, r, tx.Tx, http.StatusBadRequest,
util.JoinErrs(errs), nil)
+ return
+ }
+
+ if inf.Config.UseIMS {
+ runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, r.Header,
queryValues, selectMaxLastUpdatedQuery(where))
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ api.AddLastModifiedHdr(w, maxTime)
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+
+ selectQuery := "SELECT id, name, last_updated FROM division d"
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil,
fmt.Errorf("Divisions read: error getting divison(s): %w", err))
+ return
+ }
+ defer log.Close(rows, "unable to close DB connection")
+
+ div := tc.DivisionV5{}
+ divList := []tc.DivisionV5{}
+ for rows.Next() {
+ if err = rows.Scan(&div.ID, &div.Name, &div.LastUpdated); err
!= nil {
+ api.HandleErr(w, r, tx.Tx,
http.StatusInternalServerError, nil, fmt.Errorf("error getting division(s):
%w", err))
+ return
+ }
+ divList = append(divList, div)
+ }
+
+ api.WriteResp(w, r, divList)
+ return
+}
+
+func CreateDivision(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+ tx := inf.Tx.Tx
+
+ div, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if division already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from division where name = $1",
div.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if division with name %s exists", err,
div.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("division name '%s' already exists", div.Name), nil)
+ return
+ }
+
+ // create division
+ query := `INSERT INTO division (name) VALUES ($1) RETURNING id,
last_updated`
+ err = tx.QueryRow(query, div.Name).Scan(&div.ID, &div.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating division with name: %s", err, div.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "division was created.")
+ w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/divisons?name=%s",
inf.Version.Major, inf.Version.Minor, div.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, div)
+ return
+}
+
+func UpdateDivision(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ tx := inf.Tx.Tx
+ div, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedID := inf.Params["id"]
+
+ intRequestId, convErr := strconv.Atoi(requestedID)
+ if convErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
fmt.Errorf("division update error: %w, while converting from string to int",
convErr), nil)
+ }
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModified(r.Header, inf.Tx,
intRequestId, "division")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name and description of a division
+ query := `UPDATE division div SET
+ name = $2
+ WHERE div.id = $1
+ RETURNING div.id, div.last_updated`
+
+ err := tx.QueryRow(query, requestedID, div.Name).Scan(&div.ID,
&div.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("division with ID: %v not found", div.ID), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "division was updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, div)
+ return
+}
+
+func DeleteDivision(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()
+
+ id := inf.Params["id"]
+ exists, err := dbhelpers.DivisionExists(tx, id)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ if !exists {
+ if id != "" {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("no divisions exists by id: %s", id), nil)
+ return
+ } else {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("no divisions exists for empty id: %s", id), nil)
+ return
+ }
+ }
+
+ assignedRegions := 0
+ if err := inf.Tx.Get(&assignedRegions, "SELECT count(id) FROM region
reg WHERE reg.division=$1", id); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("Divisions delete, counting assigned Regions: %w", err))
+ return
+ } else if assignedRegions != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can
not delete a division with %d assigned region", assignedRegions), nil)
+ return
+ }
+
+ res, err := tx.Exec("DELETE FROM division AS div WHERE div.id=$1", id)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ rowsAffected, err := res.RowsAffected()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("determining rows affected for delete division: %w", err))
+ return
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("no rows deleted for division"), nil)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "division was deleted.")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, inf.Params)
+ return
+}
+
+// selectMaxLastUpdatedQuery used for TryIfModifiedSinceQuery()
+func selectMaxLastUpdatedQuery(where string) string {
+ return `SELECT max(t) from (
+ SELECT max(a.last_updated) as t from division a` + where +
+ ` UNION ALL
+ select max(last_updated) as t from last_deleted l where
l.table_name='division') as res`
+}
+
+func readAndValidateJsonStruct(r *http.Request) (tc.DivisionV5, error) {
+ var div tc.DivisionV5
+ if err := json.NewDecoder(r.Body).Decode(&div); err != nil {
+ userErr := fmt.Errorf("error decoding POST request body into
DivisionV5 struct %w", err)
+ return div, userErr
+ }
+
+ // validate JSON body
+ rule :=
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist
of only alphanumeric, dash, or underscore characters")
+ errs := tovalidate.ToErrors(validation.Errors{
+ "name": validation.Validate(div.Name, validation.Required,
rule),
+ })
+ if len(errs) > 0 {
+ userErr := util.JoinErrs(errs)
+ return div, userErr
+ }
+ return div, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 396db1766c..ba2f12d167 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -214,10 +214,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `dbdump/?`, Handler: dbdump.DBDump, RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"DBDUMP:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 42401664731},
//Division: CRUD
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `divisions/?$`, Handler:
api.ReadHandler(&division.TODivision{}), RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"DIVISION:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 408518153431},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `divisions/{id}$`, Handler:
api.UpdateHandler(&division.TODivision{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:UPDATE",
"DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
40636914031},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `divisions/?$`, Handler:
api.CreateHandler(&division.TODivision{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:CREATE",
"DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
45371380031},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `divisions/{id}$`, Handler:
api.DeleteHandler(&division.TODivision{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:DELETE",
"DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
432538223731},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `divisions/?$`, Handler: division.GetDivisions,
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
408518153431},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `divisions/{id}$`, Handler: division.UpdateDivision,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"DIVISION:UPDATE", "DIVISION:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 40636914031},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `divisions/?$`, Handler: division.CreateDivision,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"DIVISION:CREATE", "DIVISION:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 45371380031},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `divisions/{id}$`, Handler: division.DeleteDivision,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"DIVISION:DELETE", "DIVISION:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 432538223731},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `logs/?$`, Handler: logs.Getv40, RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"LOG:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 44834055031},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `logs/newcount/?$`, Handler: logs.GetNewCount,
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"LOG:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
440583301231},
diff --git a/traffic_ops/v5-client/division.go
b/traffic_ops/v5-client/division.go
index 880fe3baa1..d0f4f9666b 100644
--- a/traffic_ops/v5-client/division.go
+++ b/traffic_ops/v5-client/division.go
@@ -26,7 +26,7 @@ import (
const apiDivisions = "/divisions"
// CreateDivision creates the given Division.
-func (to *Session) CreateDivision(division tc.Division, opts RequestOptions)
(tc.Alerts, toclientlib.ReqInf, error) {
+func (to *Session) CreateDivision(division tc.DivisionV5, opts RequestOptions)
(tc.Alerts, toclientlib.ReqInf, error) {
var alerts tc.Alerts
reqInf, err := to.post(apiDivisions, opts, division, &alerts)
return alerts, reqInf, err
@@ -34,7 +34,7 @@ func (to *Session) CreateDivision(division tc.Division, opts
RequestOptions) (tc
// UpdateDivision replaces the Division identified by 'id' with the one
// provided.
-func (to *Session) UpdateDivision(id int, division tc.Division, opts
RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
+func (to *Session) UpdateDivision(id int, division tc.DivisionV5, opts
RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
route := fmt.Sprintf("%s/%d", apiDivisions, id)
var alerts tc.Alerts
reqInf, err := to.put(route, opts, division, &alerts)
@@ -42,8 +42,8 @@ func (to *Session) UpdateDivision(id int, division
tc.Division, opts RequestOpti
}
// GetDivisions returns Divisions from Traffic Ops.
-func (to *Session) GetDivisions(opts RequestOptions) (tc.DivisionsResponse,
toclientlib.ReqInf, error) {
- var data tc.DivisionsResponse
+func (to *Session) GetDivisions(opts RequestOptions) (tc.DivisionsResponseV5,
toclientlib.ReqInf, error) {
+ var data tc.DivisionsResponseV5
reqInf, err := to.get(apiDivisions, opts, &data)
return data, reqInf, err
}