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

rshah 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 7ce9214315 Fixes ProfileParameters V5 apis to respond with RFC3339 
date/time Format (#7738)
7ce9214315 is described below

commit 7ce921431500b7fd0c567c5d0726c35ca7f78f25
Author: Jagan Parthiban <[email protected]>
AuthorDate: Thu Aug 24 07:29:42 2023 +0530

    Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format 
(#7738)
    
    * RFC3339 Changes for ProfileParameters. Fixes 
https://github.com/apache/trafficcontrol/issues/7737
    
    * CHANGELOG.md entry added
    
    * Fixed PR review comments
    
    * Fixed PR review comments
    
    * Remove strconv function and use APIInfo
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/v5/profileparameters.rst           |  79 ++++--
 lib/go-tc/parameters.go                            |  33 +++
 lib/go-tc/profile_parameters.go                    |  36 +++
 traffic_ops/testing/api/v5/cdn_locks_test.go       |   2 +-
 .../testing/api/v5/profile_parameters_test.go      |   2 +-
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     |  15 +
 .../profileparameter/profile_parameters.go         | 302 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing/routes.go   |   8 +-
 traffic_ops/v5-client/profile_parameter.go         |   4 +-
 11 files changed, 458 insertions(+), 26 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16eaf2d80f..1a46e6c808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,6 +77,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#7742](https://github.com/apache/trafficcontrol/pull/7742) *Traffic Ops* 
Changed api tests to supply the absolute path of certs.
 
 ### Fixed
+- [#7738](https://github.com/apache/trafficcontrol/pull/7738) *Traffic Ops* 
Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format
 - [#7730](https://github.com/apache/trafficcontrol/pull/7730) *Traffic 
Monitor* Fixed the panic seen in TM when `plugin.system_stats.timestamp_ms` 
appears as float and not string.
 - [#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.
 - [#7690](https://github.com/apache/trafficcontrol/pull/7690) *Traffic Ops* 
Fixes Logs V5 api to respond with RFC3339 timestamps.
diff --git a/docs/source/api/v5/profileparameters.rst 
b/docs/source/api/v5/profileparameters.rst
index 58e184d3ae..efdd82a354 100644
--- a/docs/source/api/v5/profileparameters.rst
+++ b/docs/source/api/v5/profileparameters.rst
@@ -52,7 +52,7 @@ Request Structure
 
 Response Structure
 ------------------
-:lastUpdated: The date and time at which this 
:term:`Profile`/:term:`Parameter` association was last modified, in 
:ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this 
:term:`Profile`/:term:`Parameter` association was last modified, in :rfc:`3339`
 :parameter:   The :ref:`parameter-id` of a :term:`Parameter` assigned to 
``profile``
 :profile:     The :ref:`profile-name` of the :term:`Profile` to which the 
:term:`Parameter` identified by ``parameter`` is assigned
 
@@ -73,12 +73,12 @@ Response Structure
 
        { "response": [
                {
-                       "lastUpdated": "2018-12-05 17:50:49+00",
+                       "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
                        "profile": "GLOBAL",
                        "parameter": 4
                },
                {
-                       "lastUpdated": "2018-12-05 17:50:49+00",
+                       "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
                        "profile": "GLOBAL",
                        "parameter": 5
                }
@@ -152,7 +152,7 @@ Array Format
 
 Response Structure
 ------------------
-:lastUpdated: The date and time at which the :term:`Profile`/:term:`Parameter` 
assignment was last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which the :term:`Profile`/:term:`Parameter` 
assignment was last modified, in :rfc:`3339`
 :parameter:   :ref:`parameter-name` of the :term:`Parameter` which is assigned 
to ``profile``
 :parameterId: The :ref:`parameter-id` of the assigned :term:`Parameter`
 :profile:     :ref:`profile-name` of the :term:`Profile` to which the 
:term:`Parameter` is assigned
@@ -173,16 +173,61 @@ Response Structure
        Date: Mon, 10 Dec 2018 13:50:11 GMT
        Content-Length: 166
 
-       { "alerts": [
-               {
-                       "text": "profileParameter was created.",
-                       "level": "success"
-               }
-       ],
-       "response": {
-               "lastUpdated": null,
-               "profile": null,
-               "profileId": 18,
-               "parameter": null,
-               "parameterId": 1
-       }}
+    {
+      "alerts": [
+        {
+          "text": "All Requested ProfileParameters were created.",
+          "level": "success"
+        }
+      ],
+      "response": [
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "testProfile1",
+          "profileId": 18,
+          "parameter": "testParam1",
+          "parameterId": 1
+        }
+      ]
+    }
+
+.. code-block:: http
+       :caption: Response Example - Array Format
+
+       HTTP/1.1 200 OK
+       Access-Control-Allow-Credentials: true
+       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
+       Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
+       Access-Control-Allow-Origin: *
+       Content-Type: application/json
+       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
+       Whole-Content-Sha512: 
eDmIwlzX44fZdxLRPHMNa8aoGAK5fQv9Y70A2eeQHfEkliU4evwcsQ4WeHcH0l3/wPTGlpyC0gwLo8LQQpUxWQ==
+       X-Server-Name: traffic_ops_golang/
+       Date: Mon, 10 Dec 2018 13:50:11 GMT
+       Content-Length: 166
+
+
+    {
+      "alerts": [
+        {
+          "text": "All Requested Profile_Parameters were created.",
+          "level": "success"
+        }
+      ],
+      "response": [
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "test1",
+          "profileId": 17,
+          "parameter": "tm.toolname",
+          "parameterId": 1091
+        },
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "test2",
+          "profileId": 18,
+          "parameter": "maxRevalDurationDays",
+          "parameterId": 1091
+        }
+      ]
+    }
\ No newline at end of file
diff --git a/lib/go-tc/parameters.go b/lib/go-tc/parameters.go
index 57cce3206f..8b6a84aa5f 100644
--- a/lib/go-tc/parameters.go
+++ b/lib/go-tc/parameters.go
@@ -27,6 +27,7 @@ import (
        "fmt"
        "strconv"
        "strings"
+       "time"
 
        "github.com/apache/trafficcontrol/lib/go-util"
 
@@ -295,6 +296,16 @@ type ProfileParametersNullable struct {
        Parameter   *int       `json:"parameter" db:"parameter_id"`
 }
 
+// ProfileParametersNullableV5 is the latest minor version of the major 
version 5
+type ProfileParametersNullableV5 ProfileParametersNullableV50
+
+// ProfileParametersNullableV50 is an object of the form returned by the 
Traffic Ops /profileparameters endpoint.
+type ProfileParametersNullableV50 struct {
+       LastUpdated *time.Time `json:"lastUpdated" db:"last_updated"`
+       Profile     *string    `json:"profile" db:"profile"`
+       Parameter   *int       `json:"parameter" db:"parameter_id"`
+}
+
 // ProfileParametersNullableResponse is the structure of a response from
 // Traffic Ops to GET requests made to its /profileparameters API endpoint.
 //
@@ -315,6 +326,18 @@ type ProfileParam struct {
        LastUpdated *TimeNoMod `json:"lastUpdated"`
 }
 
+// ProfileParamV5 is the latest minor version of the major version 5
+type ProfileParamV5 ProfileParamV50
+
+// ProfileParamV50 is a relationship between a Profile and some Parameter
+// assigned to it as it appears in the Traffic Ops API's responses to the
+// /profileparameters endpoint.
+type ProfileParamV50 struct {
+       Parameter   int        `json:"parameter"`
+       Profile     string     `json:"profile"`
+       LastUpdated *time.Time `json:"lastUpdated"`
+}
+
 // ProfileParameterCreationRequest is the type of data accepted by Traffic
 // Ops as payloads in POST requests to its /profileparameters endpoint.
 type ProfileParameterCreationRequest struct {
@@ -329,6 +352,16 @@ type ProfileParametersAPIResponse struct {
        Alerts
 }
 
+// ProfileParametersAPIResponseV5 is the latest minor version of the major 
version 5
+type ProfileParametersAPIResponseV5 ProfileParametersAPIResponseV50
+
+// ProfileParametersAPIResponseV50 is the type of a response from Traffic Ops 
to
+// requests made to its /profileparameters endpoint.
+type ProfileParametersAPIResponseV50 struct {
+       Response []ProfileParamV5 `json:"response"`
+       Alerts
+}
+
 // ProfileExportImportParameterNullable is an object of the form used by 
Traffic Ops
 // to represent parameters for exported and imported profiles.
 type ProfileExportImportParameterNullable struct {
diff --git a/lib/go-tc/profile_parameters.go b/lib/go-tc/profile_parameters.go
index f4ff9d7070..7cbef1ccc8 100644
--- a/lib/go-tc/profile_parameters.go
+++ b/lib/go-tc/profile_parameters.go
@@ -19,6 +19,8 @@ package tc
  * under the License.
  */
 
+import "time"
+
 // ProfileParametersResponse is the type of the response from Traffic Ops to
 // GET requests made to its /profileparameters API endpoint.
 type ProfileParametersResponse struct {
@@ -59,3 +61,37 @@ type ProfileParameterNullable struct {
        Parameter   *string    `json:"parameter" db:"parameter"`
        ParameterID *int       `json:"parameterId" db:"parameter_id"`
 }
+
+// ProfileParametersResponseV5 is the type of the response from Traffic Ops to
+// GET requests made to its /profileparameters API endpoint.
+type ProfileParametersResponseV5 struct {
+       Response []ProfileParameterV5 `json:"response"`
+       Alerts
+}
+
+// ProfileParameterResponseV5 is a single ProfileParameter response for Create 
to
+// depict what changed.
+// swagger:response ProfileParameterResponse
+// in: body
+type ProfileParameterResponseV5 struct {
+       // in: body
+       Response ProfileParameterV5 `json:"response"`
+       Alerts
+}
+
+// ProfileParameterV5 is the latest minor version of the major version 5
+type ProfileParameterV5 ProfileParameterV50
+
+// ProfileParameterV50 is a representation of a relationship between a 
Parameter
+// and a Profile to which it is assigned.
+//
+// Note that not all unique identifiers for each represented object in this
+// relationship structure are guaranteed to be populated by the Traffic Ops
+// API.
+type ProfileParameterV50 struct {
+       LastUpdated time.Time `json:"lastUpdated"`
+       Profile     string    `json:"profile"`
+       ProfileID   int       `json:"profileId"`
+       Parameter   string    `json:"parameter"`
+       ParameterID int       `json:"parameterId"`
+}
diff --git a/traffic_ops/testing/api/v5/cdn_locks_test.go 
b/traffic_ops/testing/api/v5/cdn_locks_test.go
index 583d22cb41..04e36a2293 100644
--- a/traffic_ops/testing/api/v5/cdn_locks_test.go
+++ b/traffic_ops/testing/api/v5/cdn_locks_test.go
@@ -326,7 +326,7 @@ func TestCDNLocks(t *testing.T) {
                                                "profileId":   GetProfileID(t, 
"EDGEInCDN2")(),
                                                "parameterId": 
GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", 
"STRING ats")(),
                                        },
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)),
                                },
                                "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": {
                                        ClientSession: TOSession,
diff --git a/traffic_ops/testing/api/v5/profile_parameters_test.go 
b/traffic_ops/testing/api/v5/profile_parameters_test.go
index d1bb2576a9..16976a8ef3 100644
--- a/traffic_ops/testing/api/v5/profile_parameters_test.go
+++ b/traffic_ops/testing/api/v5/profile_parameters_test.go
@@ -68,7 +68,7 @@ func TestProfileParameters(t *testing.T) {
                                                        },
                                                },
                                        },
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)),
                                },
                                "BAD REQUEST when INVALID PROFILEID and 
PARAMETERID": {
                                        ClientSession: TOSession,
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go 
b/traffic_ops/testing/api/v5/traffic_control_test.go
index 4d9db708b5..6fa619b368 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -41,7 +41,7 @@ type TrafficControl struct {
        Origins                                           []tc.Origin           
                  `json:"origins"`
        Profiles                                          []tc.Profile          
                  `json:"profiles"`
        Parameters                                        []tc.Parameter        
                  `json:"parameters"`
-       ProfileParameters                                 []tc.ProfileParameter 
                  `json:"profileParameters"`
+       ProfileParameters                                 
[]tc.ProfileParameterV5                 `json:"profileParameters"`
        PhysLocations                                     []tc.PhysLocationV5   
                  `json:"physLocations"`
        Regions                                           []tc.RegionV5         
                  `json:"regions"`
        Roles                                             []tc.RoleV4           
                  `json:"roles"`
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go 
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index c70056279b..464502c2d2 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -2304,3 +2304,18 @@ func DeleteCoordinate(tx *sql.Tx, cacheGroupID int, 
coordinateID int) error {
        }
        return nil
 }
+
+// ProfileParameterExists confirms whether the ProfileParameter exists, and an 
error (if one occurs).
+func ProfileParameterExists(tx *sql.Tx, profileID string, parameterID string) 
(bool, error) {
+       var count int
+       if err := tx.QueryRow("SELECT count(*) FROM profile_parameter WHERE 
profile=$1 and parameter=$2", profileID, parameterID).Scan(&count); err != nil {
+               return false, fmt.Errorf("error getting profile_parameter info: 
%w", err)
+       }
+       if count == 0 {
+               return false, nil
+       }
+       if count != 1 {
+               return false, fmt.Errorf("getting profile_parameter info - 
expected row count: 1, actual: %d", count)
+       }
+       return true, nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go 
b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
index 85846682f3..507f806e9a 100644
--- a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
@@ -20,16 +20,23 @@ package profileparameter
  */
 
 import (
+       "database/sql"
+       "encoding/json"
        "errors"
+       "fmt"
+       "io"
        "net/http"
+       "reflect"
        "strconv"
        "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"
 )
@@ -212,3 +219,298 @@ func deleteQuery() string {
        WHERE profile=:profile_id and parameter=:parameter_id`
        return query
 }
+
+func GetProfileParameter(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{
+               "profileId":   {Column: "pp.profile"},
+               "parameterId": {Column: "pp.parameter"},
+               "lastUpdated": {Column: "pp.last_updated"},
+       }
+       if _, ok := inf.Params["orderby"]; !ok {
+               inf.Params["orderby"] = "parameter"
+       }
+       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")
+       }
+
+       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("Profile Parameter read: error getting Profile Parameter(s): %w", 
err))
+               return
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       profileParams := tc.ProfileParametersNullableV5{}
+       profileParamsList := []tc.ProfileParametersNullableV5{}
+       for rows.Next() {
+               if err = rows.Scan(&profileParams.LastUpdated, 
&profileParams.Parameter, &profileParams.Profile); err != nil {
+                       api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("error getting profile 
parameter(s): %w", err))
+                       return
+               }
+
+               profileParamsList = append(profileParamsList, profileParams)
+       }
+
+       api.WriteResp(w, r, profileParamsList)
+       return
+}
+
+func CreateProfileParameter(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
+
+       body, err := io.ReadAll(r.Body)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("error reading request body"), nil)
+               return
+       }
+       defer r.Body.Close()
+
+       // Initial Unmarshal to validate request body
+       var data interface{}
+       err = json.Unmarshal(body, &data)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("invalid request format"), nil)
+               return
+       }
+
+       // This code block decides if the request body is a slice of parameters 
or a single object.
+       var profileParams []tc.ProfileParameterCreationRequest
+       switch reflect.TypeOf(data).Kind() {
+       case reflect.Slice:
+               if err := json.Unmarshal(body, &profileParams); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("error unmarshalling slice"), nil)
+                       return
+               }
+       case reflect.Map:
+               // If it is a single object it is still converted to a slice 
for code simplicity.
+               var profileParam tc.ProfileParameterCreationRequest
+               if err := json.Unmarshal(body, &profileParam); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("error unmarshalling single object"), nil)
+                       return
+               }
+               profileParams = append(profileParams, profileParam)
+       default:
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("invalid request format"), nil)
+               return
+       }
+
+       // Validate all objects of the every profile parameter from the request 
slice
+       for _, profileParameter := range profileParams {
+               readValErr := validateRequestProfileParameter(profileParameter)
+               if readValErr != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
readValErr, nil)
+                       return
+               }
+       }
+
+       // Check user Permissions on all Profiles requested
+       for _, profileParameter := range profileParams {
+               cdnName, err := dbhelpers.GetCDNNameFromProfileID(tx, 
profileParameter.ProfileID)
+               if err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
err, nil)
+                       return
+               }
+               userErr, sysErr, errCode = 
dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, string(cdnName), inf.User.UserName)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+                       return
+               }
+       }
+
+       // Check if any of the profile parameter from the request slice already 
exists
+       for _, profileParameter := range profileParams {
+               var count int
+               err = tx.QueryRow("SELECT count(*) from profile_parameter where 
profile = $1 and parameter = $2", profileParameter.ProfileID, 
profileParameter.ParameterID).Scan(&count)
+               if err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, fmt.Errorf("error: %w, when checking if profile parameter with profile_id 
%d and parameter_id %d exists", err, profileParameter.ProfileID, 
profileParameter.ParameterID))
+                       return
+               }
+               if count == 1 {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("profile parameter with profile_id %d and parameter_id %d already 
exists", profileParameter.ProfileID, profileParameter.ParameterID), nil)
+                       return
+               }
+       }
+
+       // Create all profile parameters from the request slice
+       var objProfileParams []tc.ProfileParameterV5
+       for _, profileParameter := range profileParams {
+               query := `
+                               INSERT INTO profile_parameter (
+                               profile, 
+                               parameter
+                               ) VALUES (
+                               $1, $2
+                               ) RETURNING profile, parameter, last_updated
+`
+               var objProfileParam tc.ProfileParameterV5
+               err = tx.QueryRow(
+                       query,
+                       profileParameter.ProfileID,
+                       profileParameter.ParameterID,
+               ).Scan(
+                       &objProfileParam.ProfileID,
+                       &objProfileParam.ParameterID,
+                       &objProfileParam.LastUpdated,
+               )
+
+               if err != nil {
+                       if errors.Is(err, sql.ErrNoRows) {
+                               api.HandleErr(w, r, tx, 
http.StatusInternalServerError, fmt.Errorf("error: %w in profile_parameter with 
with profile_id %d and parameter_id %d", err, profileParameter.ProfileID, 
profileParameter.ParameterID), nil)
+                               return
+                       }
+                       usrErr, sysErr, code := api.ParseDBError(err)
+                       api.HandleErr(w, r, tx, code, usrErr, sysErr)
+                       return
+               }
+
+               // Fetch the Profile Name from ID to insert in type 
ProfileParameterV5
+               profileName, ok, err := 
dbhelpers.GetProfileNameFromID(profileParameter.ProfileID, tx)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("getting profile name from id: 
"+err.Error()))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("profile not found"), nil)
+                       return
+               }
+
+               // Fetch the Parameter Name from ID to insert in type 
ProfileParameterV5
+               parameterName, ok, err := dbhelpers.GetParamNameByID(tx, 
profileParameter.ParameterID)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("getting parameter name from 
id: "+err.Error()))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
errors.New("parameter not found"), nil)
+                       return
+               }
+
+               objProfileParam.Profile = profileName
+               objProfileParam.Parameter = parameterName
+               objProfileParams = append(objProfileParams, objProfileParam)
+       }
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "All Requested 
ProfileParameters were created.")
+       api.WriteAlertsObj(w, r, http.StatusCreated, alerts, objProfileParams)
+       return
+
+}
+
+func DeleteProfileParameter(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, 
[]string{"profileId"})
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       profileID := inf.Params["profileId"]
+       parameterID := inf.Params["parameterId"]
+       intProfileID := inf.IntParams["profileId"]
+
+       if profileID == "" || parameterID == "" {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("couldn't delete Profile_Parameter. profileID  & parameterID Cannot 
be empty for Delete Operation"), nil)
+               return
+       }
+
+       cdnName, err := dbhelpers.GetCDNNameFromProfileID(tx, intProfileID)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+       userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, 
string(cdnName), inf.User.UserName)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       exists, err := dbhelpers.ProfileParameterExists(tx, profileID, 
parameterID)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
err)
+               return
+       }
+       if !exists {
+               api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no 
profile_parameter exists by profile_id: %s & parameter_id: %s", profileID, 
parameterID), nil)
+               return
+       }
+
+       res, err := tx.Exec("DELETE FROM profile_parameter AS pp WHERE 
pp.profile=$1 and parameter=$2", profileID, parameterID)
+       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("error determining rows affected for delete profile_parameter: %w", 
err))
+               return
+       }
+       if rowsAffected == 0 {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, 
fmt.Errorf("no rows deleted for profile_parameter"), nil)
+               return
+       }
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "profile_parameter"+
+               " was deleted.")
+       api.WriteAlerts(w, r, http.StatusOK, alerts)
+       return
+}
+
+func selectMaxLastUpdatedQuery(where string) string {
+       return `
+        SELECT max(t) from (
+            SELECT max(pp.last_updated) as t FROM profile_parameter pp
+            JOIN profile prof ON prof.id = pp.profile
+            JOIN parameter param ON param.id = pp.parameter ` + where +
+               ` UNION ALL
+            SELECT max(last_updated) as t FROM last_deleted l WHERE 
l.table_name = 'profile_parameter'
+        ) as res
+    `
+}
+
+// validateRequestProfileParameter validate the JSON objects
+func validateRequestProfileParameter(profileParameter 
tc.ProfileParameterCreationRequest) error {
+       errs := make(map[string]error)
+
+       errs[ProfileIDQueryParam] = 
validation.Validate(profileParameter.ProfileID, validation.Required)
+       errs[ParameterIDQueryParam] = 
validation.Validate(profileParameter.ParameterID, validation.Required)
+
+       if len(errs) > 0 {
+               var errorSlice []error
+               for _, err := range errs {
+                       errorSlice = append(errorSlice, err)
+               }
+               userErr := util.JoinErrs(errorSlice)
+               return userErr
+       }
+       return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 0a6285cca1..8412bf74f7 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -438,11 +438,11 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `profiles/name/{name}/parameters/?$`, Handler: 
profileparameter.GetProfileName, RequiredPrivLevel: auth.PrivLevelReadOnly, 
RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 426773783231},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profiles/name/{name}/parameters/?$`, Handler: 
profileparameter.PostProfileParamsByName, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", 
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 435594558231},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profiles/{id}/parameters/?$`, Handler: 
profileparameter.PostProfileParamsByID, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", 
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 41681870831},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `profileparameters/?$`, Handler: 
api.ReadHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", 
"PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
45060980531},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profileparameters/?$`, Handler: 
api.CreateHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", 
"PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 42880969331},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profileparameter/?$`, Handler: 
profileparameter.PostProfileParam, RequiredPrivLevel: auth.PrivLevelOperations, 
RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", 
"PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 
42427531},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `parameterprofile/?$`, Handler: 
profileparameter.PostParamProfile, RequiredPrivLevel: auth.PrivLevelOperations, 
RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", 
"PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
408061086131},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, 
Handler: api.DeleteHandler(&profileparameter.TOProfileParameter{}), 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 42483952931},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profileparameter/?$`, Handler: 
profileparameter.PostProfileParam, RequiredPrivLevel: auth.PrivLevelOperations, 
RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", 
"PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 
42427531},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `profileparameters/?$`, Handler: 
profileparameter.GetProfileParameter, RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", 
"PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
45060980531},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `profileparameters/?$`, Handler: 
profileparameter.CreateProfileParameter, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", 
"PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 42880969331},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, 
Handler: profileparameter.DeleteProfileParameter, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", 
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 42483952931},
 
                //Tenants
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `tenants/?$`, Handler: 
api.ReadHandler(&apitenant.TOTenant{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"TENANT:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 467796781431},
diff --git a/traffic_ops/v5-client/profile_parameter.go 
b/traffic_ops/v5-client/profile_parameter.go
index e0adfcbb6e..d0720520b8 100644
--- a/traffic_ops/v5-client/profile_parameter.go
+++ b/traffic_ops/v5-client/profile_parameter.go
@@ -56,8 +56,8 @@ func (to *Session) CreateProfileWithMultipleParameters(pps 
tc.PostProfileParam,
 }
 
 // GetProfileParameters retrieves associations between Profiles and Parameters.
-func (to *Session) GetProfileParameters(opts RequestOptions) 
(tc.ProfileParametersAPIResponse, toclientlib.ReqInf, error) {
-       var data tc.ProfileParametersAPIResponse
+func (to *Session) GetProfileParameters(opts RequestOptions) 
(tc.ProfileParametersAPIResponseV5, toclientlib.ReqInf, error) {
+       var data tc.ProfileParametersAPIResponseV5
        reqInf, err := to.get(apiProfileParameters, opts, &data)
        return data, reqInf, err
 }

Reply via email to