ocket8888 commented on code in PR #7408:
URL: https://github.com/apache/trafficcontrol/pull/7408#discussion_r1163148453
##########
docs/source/api/index.rst:
##########
@@ -98,6 +98,42 @@ Traffic Ops will often return responses from its API that
include dates. As of t
2021-06-07 08:01:02+00
+.. _rfc-3339-datetime:
+
+RFC 3339 Date/Time Format
+-------------------------------------
+Detailed description of RFC 3339 date and time format can be found `here
<https://www.rfc-editor.org/rfc/rfc3339>`_. The ``date-time`` is constructed as
follows,
+
+.. code-block:: text
+ :caption: date-time construction in RFC3339
+
+ date-fullyear = 4DIGIT
+ date-month = 2DIGIT ; 01-12
+ date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
+
+ time-hour = 2DIGIT ; 00-23
+ time-minute = 2DIGIT ; 00-59
+ time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
+
+ time-secfrac = "." 1*DIGIT
+
+ time-numoffset = ("+" / "-") time-hour ":" time-minute
+ time-offset = "Z" / time-numoffset
+
+ partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
+ full-time = partial-time time-offset
+
+ full-date = date-fullyear "-" date-month "-" date-mday
+
+ date-time = full-date "T" full-time
+
+.. code-block:: text
+ :caption: Example RFC3339 date-time
+
+ 2023-03-29T19:43:17.557642+05:30
+.. note:: Refactoring Traffic Ops API services to respond with RFC 3339 format
is still work in progress.
+
+
Review Comment:
We don't need this section. It's not our job to tell people what RFC3339 is;
that's RFC3339's job.
##########
docs/source/api/v5/service_categories.rst:
##########
@@ -121,7 +121,7 @@ Request Structure
Response Structure
------------------
:name: This :term:`Service Category`'s name
-:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`rfc-3339-datetime`
Review Comment:
same as above
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
Review Comment:
`errors.New(err.Error())` should just be `err`.
##########
docs/source/api/v5/service_categories.rst:
##########
@@ -64,7 +64,7 @@ Request Structure
Response Structure
------------------
:name: This :term:`Service Category`'s name
-:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`rfc-3339-datetime`
Review Comment:
There's a built-in text role for referencing RFCs: `rfc`. E.g.
```rst
... in :rfc:3339
```
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
Review Comment:
same as above RE: error wrapping
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
Review Comment:
so, this query only returns one row, right? So can we not use something like
`QueryRow` to avoid the unnecessary iteration?
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if service category with name %s exists",
err, sc.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category name '%s' already exists.", sc.Name), nil)
+ return
+ }
+
+ // create service category
+ query := `INSERT INTO service_category (name) VALUES ($1) RETURNING
name, last_updated`
+ err = tx.QueryRow(query, sc.Name).Scan(&sc.Name, &sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating service category with name: %s", err,
sc.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/service_category?name=%s", inf.Version.Major,
inf.Version.Minor, sc.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+ return
+}
+
+// UpdateServiceCategory [Version : V5] function updates the name of the
service category passed.
+func UpdateServiceCategory(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
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedName := inf.Params["name"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header,
inf.Tx, requestedName, "service_category")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name of a service category
+ query := `UPDATE service_category sc SET
+ name = $1
+ WHERE sc.name = $2
+ RETURNING sc.name, sc.last_updated`
+
+ err := tx.QueryRow(query, sc.Name, requestedName).Scan(&sc.Name,
&sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category with name: %s not found", requestedName), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+ return
+}
+
+// DeleteServiceCategory [Version : V5] function deletes the service category
passed.
+func DeleteServiceCategory(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()
+
+ name := inf.Params["name"]
+ exists, err := dbhelpers.GetServiceCategoryInfo(tx, name)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ if !exists {
+ if name != "" {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("no service category exists by name: %s", name), nil)
+ return
+ } else {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("no service category exists for empty name: %s", name), nil)
+ return
+ }
Review Comment:
Why the special handling for an empty name?
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories_test.go:
##########
@@ -0,0 +1,144 @@
+package servicecategory
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+ "github.com/apache/trafficcontrol/lib/go-rfc"
+ "github.com/jmoiron/sqlx"
+ "gopkg.in/DATA-DOG/go-sqlmock.v1"
+ "net/http"
+ "testing"
+ "time"
Review Comment:
same as above RE: import grouping/order
##########
docs/source/api/v5/service_categories.rst:
##########
@@ -78,13 +78,13 @@ Response Structure
Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54
GMT; Max-Age=3600; HttpOnly
Whole-Content-Sha512:
Yzr6TfhxgpZ3pbbrr4TRG4wC3PlnHDDzgs2igtz/1ppLSy2MzugqaGW4y5yzwzl5T3+7q6HWej7GQZt1XIVeZQ==
X-Server-Name: traffic_ops_golang/
- Date: Wed, 11 Mar 2020 20:02:47 GMT
+ Date: Wed, 29 Mar 2023 15:56:34 GMT
Content-Length: 102
{
"response": [
{
- "lastUpdated": "2020-03-04 15:46:20-07",
+ "lastUpdated":
"2023-03-29T19:43:17.557642+05:30",
Review Comment:
How did you get this example? I've never seen TO spit out any dates that
aren't in UTC.
##########
traffic_ops/v5-client/serviceCategory.go:
##########
@@ -28,24 +28,26 @@ import (
const apiServiceCategories = "/service_categories"
// CreateServiceCategory creates the given Service Category.
-func (to *Session) CreateServiceCategory(serviceCategory tc.ServiceCategory,
opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
+// tc.ServiceCategoryV5 used RFC3339 Time Format
Review Comment:
I don't think we need to call that out here. It's a different struct, and it
can be different in any way in the future, not just in timestamp JSON encoding
format.
##########
lib/go-tc/service_category.go:
##########
@@ -37,3 +39,25 @@ type ServiceCategory struct {
LastUpdated TimeNoMod `json:"lastUpdated" db:"last_updated"`
Name string `json:"name" db:"name"`
}
+
+// ServiceCategoriesResponseV5 is a list of Service Categories as a response.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoriesResponseV5 struct {
+ Response []ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryResponseV5 is a single Service Category response for Update
and Create to
+// depict what changed.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoryResponseV5 struct {
+ Response ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryV5 holds the name, id and associated tenant that comprise a
service category.
Review Comment:
Service Categories are not comprised of an ID and/or an associated Tenant.
They only have a name and a modification timestamp.
##########
docs/source/api/v5/service_categories_name.rst:
##########
@@ -58,7 +58,7 @@ Request Structure
Response Structure
------------------
:name: This :term:`Service Category`'s name
-:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this :term:`Service Category` was
last modified, in :ref:`rfc-3339-datetime`
Review Comment:
same as above RE: text role
##########
lib/go-tc/service_category.go:
##########
@@ -1,5 +1,7 @@
package tc
+import "time"
+
Review Comment:
we typically place imports after the license header
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -23,6 +23,10 @@ import (
"database/sql"
"encoding/json"
"errors"
+ "fmt"
+ "github.com/apache/trafficcontrol/lib/go-log"
+ "github.com/apache/trafficcontrol/lib/go-rfc"
+ "github.com/jmoiron/sqlx"
Review Comment:
The order of imports should be
1. Standard libraries (and `golang.org/x/`)
2. Project-internal imports
3. external libraries
##########
lib/go-tc/service_category.go:
##########
@@ -37,3 +39,25 @@ type ServiceCategory struct {
LastUpdated TimeNoMod `json:"lastUpdated" db:"last_updated"`
Name string `json:"name" db:"name"`
}
+
+// ServiceCategoriesResponseV5 is a list of Service Categories as a response.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoriesResponseV5 struct {
+ Response []ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryResponseV5 is a single Service Category response for Update
and Create to
+// depict what changed.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoryResponseV5 struct {
+ Response ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryV5 holds the name, id and associated tenant that comprise a
service category.
+// Previous versions hold Depreciated TimeNodMod Format. This version is
updated to RFC3339 Time Format.
Review Comment:
I don't really think this line is necessary, but if you disagree it's not
going to hurt anything to leave it in.
##########
traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go:
##########
@@ -2187,3 +2187,18 @@ func GetSCInfo(tx *sql.Tx, name string) (bool, error) {
}
return true, nil
}
+
+// GetServiceCategoryInfo confirms whether the service category exists, and an
error (if one occurs).
+func GetServiceCategoryInfo(tx *sql.Tx, name string) (bool, error) {
Review Comment:
According to both the code and the GoDoc comment this doesn't actually get
Service Category information. It just tells you if it exists, so a more
appropriate name might be `ServiceCategoryExists` or something.
##########
lib/go-tc/service_category.go:
##########
@@ -37,3 +39,25 @@ type ServiceCategory struct {
LastUpdated TimeNoMod `json:"lastUpdated" db:"last_updated"`
Name string `json:"name" db:"name"`
}
+
+// ServiceCategoriesResponseV5 is a list of Service Categories as a response.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoriesResponseV5 struct {
+ Response []ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryResponseV5 is a single Service Category response for Update
and Create to
+// depict what changed.
+// Uses ServiceCategoryV5 struct for RFC3339 Format
+type ServiceCategoryResponseV5 struct {
+ Response ServiceCategoryV5 `json:"response"`
+ Alerts
+}
+
+// ServiceCategoryV5 holds the name, id and associated tenant that comprise a
service category.
+// Previous versions hold Depreciated TimeNodMod Format. This version is
updated to RFC3339 Time Format.
+type ServiceCategoryV5 struct {
Review Comment:
So, the way our struct naming system works is we have a specific version
that never changes, and then an alias for the most recent version. So in this
case, we'd want to have a `ServiceCategoryV50` that looks like what you have
here, and then a type named `ServiceCategoryV5` that is just an alias of that
like
```go
type ServiceCategoryV5 = ServiceCategoryV50
```
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
Review Comment:
creating an error from an error should wrap the original to preserve error
identity.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
Review Comment:
comparing errors should use `errors.Is`
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
Review Comment:
Same as above RE: error-wrapping.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
Review Comment:
We don't need to return a pointer to a `time.Time`. A pointer is not any
less expensive to return than a value for that type, and the function only
returns `nil` when an error occurs, in which case the result won't be used.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
Review Comment:
`error` should be the last value returned from a function.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
Review Comment:
Instead of scanning into an int for the number of matches, since we only
care about the existence of the record you can just use `EXISTS` to get back a
boolean that tells you exactly what you want to know with no further work
required.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if service category with name %s exists",
err, sc.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category name '%s' already exists.", sc.Name), nil)
+ return
+ }
+
+ // create service category
+ query := `INSERT INTO service_category (name) VALUES ($1) RETURNING
name, last_updated`
+ err = tx.QueryRow(query, sc.Name).Scan(&sc.Name, &sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating service category with name: %s", err,
sc.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/service_category?name=%s", inf.Version.Major,
inf.Version.Minor, sc.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+ return
+}
+
+// UpdateServiceCategory [Version : V5] function updates the name of the
service category passed.
+func UpdateServiceCategory(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
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedName := inf.Params["name"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header,
inf.Tx, requestedName, "service_category")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name of a service category
+ query := `UPDATE service_category sc SET
+ name = $1
+ WHERE sc.name = $2
+ RETURNING sc.name, sc.last_updated`
+
+ err := tx.QueryRow(query, sc.Name, requestedName).Scan(&sc.Name,
&sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category with name: %s not found", requestedName), nil)
Review Comment:
The response code when a resource isn't found should be `404 Not Found`
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if service category with name %s exists",
err, sc.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category name '%s' already exists.", sc.Name), nil)
+ return
+ }
+
+ // create service category
+ query := `INSERT INTO service_category (name) VALUES ($1) RETURNING
name, last_updated`
+ err = tx.QueryRow(query, sc.Name).Scan(&sc.Name, &sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating service category with name: %s", err,
sc.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/service_category?name=%s", inf.Version.Major,
inf.Version.Minor, sc.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+ return
+}
+
+// UpdateServiceCategory [Version : V5] function updates the name of the
service category passed.
+func UpdateServiceCategory(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
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedName := inf.Params["name"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header,
inf.Tx, requestedName, "service_category")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name of a service category
+ query := `UPDATE service_category sc SET
+ name = $1
+ WHERE sc.name = $2
+ RETURNING sc.name, sc.last_updated`
+
+ err := tx.QueryRow(query, sc.Name, requestedName).Scan(&sc.Name,
&sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category with name: %s not found", requestedName), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+ return
+}
+
+// DeleteServiceCategory [Version : V5] function deletes the service category
passed.
+func DeleteServiceCategory(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()
+
+ name := inf.Params["name"]
+ exists, err := dbhelpers.GetServiceCategoryInfo(tx, name)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ if !exists {
+ if name != "" {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("no service category exists by name: %s", name), nil)
+ return
+ } else {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("no service category exists for empty name: %s", name), nil)
+ return
+ }
+ }
+
+ assignedDeliveryService := 0
+ if err := inf.Tx.Get(&assignedDeliveryService, "SELECT
count(service_category) FROM deliveryservice d WHERE d.service_category=$1",
name); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("service category delete, counting assigned servers: %w", err))
+ return
+ } else if assignedDeliveryService != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can
not delete a service category with %d assigned delivery service",
assignedDeliveryService), nil)
Review Comment:
"Delivery Service" should be plural in that error message (or
parenthetically so e.g. "Delivery Service(s)")
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if service category with name %s exists",
err, sc.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category name '%s' already exists.", sc.Name), nil)
+ return
+ }
+
+ // create service category
+ query := `INSERT INTO service_category (name) VALUES ($1) RETURNING
name, last_updated`
+ err = tx.QueryRow(query, sc.Name).Scan(&sc.Name, &sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating service category with name: %s", err,
sc.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/service_category?name=%s", inf.Version.Major,
inf.Version.Minor, sc.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+ return
+}
+
+// UpdateServiceCategory [Version : V5] function updates the name of the
service category passed.
+func UpdateServiceCategory(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
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedName := inf.Params["name"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header,
inf.Tx, requestedName, "service_category")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name of a service category
+ query := `UPDATE service_category sc SET
+ name = $1
+ WHERE sc.name = $2
+ RETURNING sc.name, sc.last_updated`
+
+ err := tx.QueryRow(query, sc.Name, requestedName).Scan(&sc.Name,
&sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category with name: %s not found", requestedName), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+ return
+}
+
+// DeleteServiceCategory [Version : V5] function deletes the service category
passed.
+func DeleteServiceCategory(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()
+
+ name := inf.Params["name"]
+ exists, err := dbhelpers.GetServiceCategoryInfo(tx, name)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ if !exists {
+ if name != "" {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("no service category exists by name: %s", name), nil)
+ return
+ } else {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("no service category exists for empty name: %s", name), nil)
+ return
+ }
+ }
+
+ assignedDeliveryService := 0
+ if err := inf.Tx.Get(&assignedDeliveryService, "SELECT
count(service_category) FROM deliveryservice d WHERE d.service_category=$1",
name); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("service category delete, counting assigned servers: %w", err))
+ return
+ } else if assignedDeliveryService != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can
not delete a service category with %d assigned delivery service",
assignedDeliveryService), nil)
+ return
+ }
+
+ res, err := tx.Exec("DELETE FROM service_category AS sc WHERE
sc.name=$1", name)
+ 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 service_category: %w", err))
+ return
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("no rows deleted for service_category"), nil)
Review Comment:
Internal Server Errors must not return details to the client. Those should
be placed in system errors.
##########
traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go:
##########
@@ -187,3 +191,317 @@ WHERE name=$2 RETURNING last_updated`
func deleteQuery() string {
return `DELETE FROM service_category WHERE name=:name`
}
+
+// Get [Version : V5] function Process the *http.Request and writes the
response. It uses GetServiceCategory function.
+func Get(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()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var err error
+ var scList []tc.ServiceCategoryV5
+
+ tx := inf.Tx
+
+ scList, err, code, maxTime = GetServiceCategory(tx, inf.Params, useIMS,
r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.ServiceCategoryV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New(err.Error()), nil)
+ return
+ }
+
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Service category errors: "+err.Error()))
+ return
+ }
+
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ }
+
+ api.WriteResp(w, r, scList)
+}
+
+// GetServiceCategory [Version : V5] receives transactions from Get function
and returns service_categories list.
+func GetServiceCategory(tx *sqlx.Tx, params map[string]string, useIMS bool,
header http.Header) ([]tc.ServiceCategoryV5, error, int, *time.Time) {
+ var runSecond bool
+ var maxTime time.Time
+ scList := []tc.ServiceCategoryV5{}
+
+ selectQuery := `SELECT name, last_updated FROM service_category as sc`
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": {Column: "sc.name", Checker: nil},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = TryIfModifiedSinceQuery(header, tx, where,
queryValues)
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return scList, nil, http.StatusNotModified, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ query := selectQuery + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, errors.New("service category read: error getting
service category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ sc := tc.ServiceCategoryV5{}
+ if err = rows.Scan(&sc.Name, &sc.LastUpdated); err != nil {
+ return nil, errors.New("error getting service
category(ies): " + err.Error()), http.StatusInternalServerError, nil
+ }
+ scList = append(scList, sc)
+ }
+
+ return scList, nil, http.StatusOK, &maxTime
+}
+
+// TryIfModifiedSinceQuery [Version : V5] function receives transactions and
header from GetServiceCategory function and returns bool value if status is not
modified.
+func TryIfModifiedSinceQuery(header http.Header, tx *sqlx.Tx, where string,
queryValues map[string]interface{}) (bool, time.Time) {
+ var max time.Time
+ var imsDate time.Time
+ var ok bool
+ imsDateHeader := []string{}
+ runSecond := true
+ dontRunSecond := false
+
+ if header == nil {
+ return runSecond, max
+ }
+
+ imsDateHeader = header[rfc.IfModifiedSince]
+ if len(imsDateHeader) == 0 {
+ return runSecond, max
+ }
+
+ if imsDate, ok = rfc.ParseHTTPDate(imsDateHeader[0]); !ok {
+ log.Warnf("IMS request header date '%s' not parsable",
imsDateHeader[0])
+ return runSecond, max
+ }
+
+ imsQuery := `SELECT max(last_updated) as t from service_category sc`
+ query := imsQuery + where
+ rows, err := tx.NamedQuery(query, queryValues)
+
+ if err != nil {
+ log.Warnf("Couldn't get the max last updated time: %v", err)
+ return runSecond, max
+ }
+
+ if err == sql.ErrNoRows {
+ return dontRunSecond, max
+ }
+
+ defer rows.Close()
+ // This should only ever contain one row
+ if rows.Next() {
+ v := time.Time{}
+ if err = rows.Scan(&v); err != nil {
+ log.Warnf("Failed to parse the max time stamp into a
struct %v", err)
+ return runSecond, max
+ }
+
+ max = v
+ // The request IMS time is later than the max of (lastUpdated,
deleted_time)
+ if imsDate.After(v) {
+ return dontRunSecond, max
+ }
+ }
+ return runSecond, max
+}
+
+// CreateServiceCategory [Version : V5] function creates the service category
with the passed name.
+func CreateServiceCategory(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
+
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if service category already exists
+ var count int
+ err := tx.QueryRow("SELECT count(*) from service_category where name =
$1 ", sc.Name).Scan(&count)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if service category with name %s exists",
err, sc.Name))
+ return
+ }
+ if count == 1 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category name '%s' already exists.", sc.Name), nil)
+ return
+ }
+
+ // create service category
+ query := `INSERT INTO service_category (name) VALUES ($1) RETURNING
name, last_updated`
+ err = tx.QueryRow(query, sc.Name).Scan(&sc.Name, &sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating service category with name: %s", err,
sc.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/service_category?name=%s", inf.Version.Major,
inf.Version.Minor, sc.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+ return
+}
+
+// UpdateServiceCategory [Version : V5] function updates the name of the
service category passed.
+func UpdateServiceCategory(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
+ sc, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ requestedName := inf.Params["name"]
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header,
inf.Tx, requestedName, "service_category")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ //update name of a service category
+ query := `UPDATE service_category sc SET
+ name = $1
+ WHERE sc.name = $2
+ RETURNING sc.name, sc.last_updated`
+
+ err := tx.QueryRow(query, sc.Name, requestedName).Scan(&sc.Name,
&sc.LastUpdated)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("service category with name: %s not found", requestedName), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "service category was
updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+ return
+}
+
+// DeleteServiceCategory [Version : V5] function deletes the service category
passed.
+func DeleteServiceCategory(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()
+
+ name := inf.Params["name"]
+ exists, err := dbhelpers.GetServiceCategoryInfo(tx, name)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ if !exists {
+ if name != "" {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("no service category exists by name: %s", name), nil)
+ return
+ } else {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("no service category exists for empty name: %s", name), nil)
+ return
+ }
+ }
+
+ assignedDeliveryService := 0
+ if err := inf.Tx.Get(&assignedDeliveryService, "SELECT
count(service_category) FROM deliveryservice d WHERE d.service_category=$1",
name); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("service category delete, counting assigned servers: %w", err))
Review Comment:
I don't think this is counting assigned servers
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]