ocket8888 commented on code in PR #7734:
URL: https://github.com/apache/trafficcontrol/pull/7734#discussion_r1303432251


##########
CHANGELOG.md:
##########
@@ -75,6 +75,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#7716](https://github.com/apache/trafficcontrol/pull/7716) *Apache Traffic 
Server* Use GCC 11 for building.
 
 ### Fixed
+- [#7734](https://github.com/apache/trafficcontrol/pull/7734) *Traffic Ops* 
*Traffic Ops* Fixed `Profiles` V5 apis to respond with `RFC3339` date/time 
Format.

Review Comment:
   Extra `*Traffic Ops*`
   
   also, the route is `/profiles` with a lowercase "p", "API" should be all 
caps, and "Format" need not start with a capital letter.



##########
traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go:
##########
@@ -2263,6 +2263,20 @@ func PhysLocationExists(tx *sql.Tx, id string) (bool, 
error) {
        return true, nil
 }
 
+func ProfileExists(tx *sql.Tx, id string) (bool, error) {

Review Comment:
   exported symbols should be described by a GoDoc comment



##########
traffic_ops/traffic_ops_golang/profile/profiles.go:
##########
@@ -360,3 +362,294 @@ type) VALUES (
 func deleteQuery() string {
        return `DELETE FROM profile WHERE id = :id`
 }
+
+// Read gets list of Profiles for APIv5
+func Read(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{
+               "cdn":   {Column: "c.id", Checker: api.IsInt},
+               "name":  {Column: "prof.name", Checker: nil},
+               "id":    {Column: "prof.id", Checker: api.IsInt},
+               "param": {Column: "pp.parameter", Checker: api.IsInt},
+       }
+
+       query := selectProfilesQuery()
+       if paramValue, ok := inf.Params["param"]; ok {
+               if len(paramValue) > 0 {
+                       query += " LEFT JOIN profile_parameter pp ON prof.id = 
pp.profile"
+               }
+       }
+
+       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 += where + orderBy + pagination
+       rows, err := tx.NamedQuery(query, queryValues)
+       if err != nil {
+               api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, 
fmt.Errorf("profile read: error getting profile(s): %w", err))
+               return
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       profile := tc.ProfileV5{}
+       var profileList []tc.ProfileV5
+       for rows.Next() {
+               if err = rows.Scan(&profile.Description, &profile.ID, 
&profile.LastUpdated, &profile.Name, &profile.RoutingDisabled, &profile.Type, 
&profile.CDNID, &profile.CDNName); err != nil {
+                       api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("error getting profile(s): %w", 
err))
+                       return
+               }
+               profileList = append(profileList, profile)
+       }
+       rows.Close()
+       profileInterfaces := []interface{}{}
+       for _, p := range profileList {
+               // Attach Parameters if the 'param' parameter is sent
+               if _, ok := inf.Params["param"]; ok {
+                       p.Parameters, err = ReadParameters(inf.Tx, inf.User, 
&p.ID)
+                       if err != nil {
+                               api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("profile read: error reading 
parameters for a profile: %w", err))
+                               return
+                       }
+               }
+               profileInterfaces = append(profileInterfaces, p)
+       }
+
+       api.WriteResp(w, r, profileInterfaces)
+       return
+}
+
+// Create a Profile for APIv5
+func Create(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
+
+       profile, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       //check if user can modify.
+       if len(strings.TrimSpace(profile.CDNName)) != 0 || profile.CDNID != 0 {
+               userErr, sysErr, statusCode := 
canProfileBeAlteredByCurrentUser(inf.User.UserName, inf.Tx.Tx, 
&profile.CDNName, &profile.CDNID)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+                       return
+               }
+       }
+
+       // check if profile already exists
+       var count int
+       err := tx.QueryRow("SELECT count(*) from profile where name=$1", 
profile.Name).Scan(&count)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("error: %w, when checking if profile '%s' exists", err, 
profile.Name))
+               return
+       }
+       if count == 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("profile:'%s' already exists", profile.Name), nil)
+               return
+       }
+
+       // create profile
+       query := `INSERT INTO profile (name, cdn, type, routing_disabled, 
description) 
+       VALUES ($1, $2, $3, $4, $5) 
+       RETURNING id, last_updated, name, description, (select name FROM cdn 
where id = $2), cdn, routing_disabled, type`
+
+       err = tx.QueryRow(query, profile.Name, profile.CDNID, profile.Type, 
profile.RoutingDisabled, profile.Description).
+               Scan(&profile.ID, &profile.LastUpdated, &profile.Name, 
&profile.Description, &profile.CDNName, &profile.CDNID, 
&profile.RoutingDisabled, &profile.Type)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
fmt.Errorf("error: %w in creating profile:%s", err, profile.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "profile was created.")
+       w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/profiles?id=%d", 
inf.Version.Major, inf.Version.Minor, profile.ID))

Review Comment:
   Just FYI, using `fmt.Sprintf("%d.%d", Version.Major, Version.Minor)` is 
exactly equivalent to just `Version.String()`. Or, to use this exact example:
   ```go
   fmt.Sprintf("/api/%d.%d/profiles?id=%d", inf.Version.Major, 
inf.Version.Minor, profile.ID)
   // is the same as
   fmt.Sprintf("/api/%s/profiles?id=%d", inf.Version, profile.ID)
   ``` 



##########
traffic_ops/traffic_ops_golang/profile/profiles.go:
##########
@@ -269,13 +271,13 @@ JOIN profile_parameter pp ON pp.parameter = p.id
 WHERE pp.profile = :profile_id`
 }
 
-func (pr *TOProfile) checkIfProfileCanBeAlteredByCurrentUser() (error, error, 
int) {
+func canProfileBeAlteredByCurrentUser(user string, tx *sql.Tx, cName *string, 
cdnID *int) (error, error, int) {
        var cdnName string
-       if pr.CDNName != nil {
-               cdnName = *pr.CDNName
+       if cName != nil {
+               cdnName = *cName
        } else {
-               if pr.CDNID != nil {
-                       cdn, ok, err := 
dbhelpers.GetCDNNameFromID(pr.ReqInfo.Tx.Tx, int64(*pr.CDNID))
+               if cdnID != nil {
+                       cdn, ok, err := dbhelpers.GetCDNNameFromID(tx, 
int64(*cdnID))

Review Comment:
   can we instead just not allow these parameters to be passed as `nil` by 
making them non-reference values?



##########
traffic_ops/traffic_ops_golang/profile/profiles.go:
##########
@@ -226,10 +228,10 @@ LEFT JOIN cdn c ON prof.cdn = c.id`
        return query
 }
 
-func ReadParameters(tx *sqlx.Tx, parameters map[string]string, user 
*auth.CurrentUser, profile tc.ProfileNullable) ([]tc.ParameterNullable, error) {
+func ReadParameters(tx *sqlx.Tx, user *auth.CurrentUser, profileID *int) 
([]tc.ParameterNullable, error) {
        privLevel := user.PrivLevel
        queryValues := make(map[string]interface{})
-       queryValues["profile_id"] = *profile.ID
+       queryValues["profile_id"] = profileID

Review Comment:
   why did we change the type of this query argument from `int` to `*int`? If 
it's `nil`, the query will always return zero results, which just means that 
it's a waste of a database call, so callers shouldn't use `ReadParameters` in 
that way.



##########
lib/go-tc/profiles.go:
##########
@@ -91,6 +92,28 @@ type Profile struct {
        Parameters      []ParameterNullable `json:"params,omitempty"`
 }
 
+// ProfilesResponseV5 is a list of profiles returned by GET requests.
+type ProfilesResponseV5 struct {
+       Response []ProfileV5 `json:"response"`
+       Alerts
+}
+
+// A ProfileV5 represents a set of configuration for a server or Delivery 
Service
+// which may be reused to allow sharing configuration across the objects to
+// which it is assigned. Note: Field LastUpdated represents RFC3339
+type ProfileV5 struct {
+       ID              int                 `json:"id" db:"id"`
+       LastUpdated     time.Time           `json:"lastUpdated" 
db:"last_updated"`
+       Name            string              `json:"name" db:"name"`
+       Parameter       string              `json:"param"`

Review Comment:
   What is this field used for?



##########
traffic_ops/traffic_ops_golang/profile/profiles.go:
##########
@@ -360,3 +362,294 @@ type) VALUES (
 func deleteQuery() string {
        return `DELETE FROM profile WHERE id = :id`
 }
+
+// Read gets list of Profiles for APIv5
+func Read(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{
+               "cdn":   {Column: "c.id", Checker: api.IsInt},
+               "name":  {Column: "prof.name", Checker: nil},
+               "id":    {Column: "prof.id", Checker: api.IsInt},
+               "param": {Column: "pp.parameter", Checker: api.IsInt},
+       }
+
+       query := selectProfilesQuery()
+       if paramValue, ok := inf.Params["param"]; ok {
+               if len(paramValue) > 0 {
+                       query += " LEFT JOIN profile_parameter pp ON prof.id = 
pp.profile"
+               }
+       }
+
+       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 += where + orderBy + pagination
+       rows, err := tx.NamedQuery(query, queryValues)
+       if err != nil {
+               api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, 
fmt.Errorf("profile read: error getting profile(s): %w", err))
+               return
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       profile := tc.ProfileV5{}
+       var profileList []tc.ProfileV5
+       for rows.Next() {
+               if err = rows.Scan(&profile.Description, &profile.ID, 
&profile.LastUpdated, &profile.Name, &profile.RoutingDisabled, &profile.Type, 
&profile.CDNID, &profile.CDNName); err != nil {
+                       api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("error getting profile(s): %w", 
err))
+                       return
+               }
+               profileList = append(profileList, profile)
+       }
+       rows.Close()
+       profileInterfaces := []interface{}{}
+       for _, p := range profileList {
+               // Attach Parameters if the 'param' parameter is sent
+               if _, ok := inf.Params["param"]; ok {
+                       p.Parameters, err = ReadParameters(inf.Tx, inf.User, 
&p.ID)
+                       if err != nil {
+                               api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("profile read: error reading 
parameters for a profile: %w", err))
+                               return
+                       }
+               }
+               profileInterfaces = append(profileInterfaces, p)
+       }
+
+       api.WriteResp(w, r, profileInterfaces)
+       return
+}
+
+// Create a Profile for APIv5
+func Create(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
+
+       profile, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       //check if user can modify.
+       if len(strings.TrimSpace(profile.CDNName)) != 0 || profile.CDNID != 0 {
+               userErr, sysErr, statusCode := 
canProfileBeAlteredByCurrentUser(inf.User.UserName, inf.Tx.Tx, 
&profile.CDNName, &profile.CDNID)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+                       return
+               }
+       }
+
+       // check if profile already exists
+       var count int
+       err := tx.QueryRow("SELECT count(*) from profile where name=$1", 
profile.Name).Scan(&count)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("error: %w, when checking if profile '%s' exists", err, 
profile.Name))
+               return
+       }
+       if count == 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("profile:'%s' already exists", profile.Name), nil)
+               return
+       }
+
+       // create profile
+       query := `INSERT INTO profile (name, cdn, type, routing_disabled, 
description) 
+       VALUES ($1, $2, $3, $4, $5) 
+       RETURNING id, last_updated, name, description, (select name FROM cdn 
where id = $2), cdn, routing_disabled, type`
+
+       err = tx.QueryRow(query, profile.Name, profile.CDNID, profile.Type, 
profile.RoutingDisabled, profile.Description).
+               Scan(&profile.ID, &profile.LastUpdated, &profile.Name, 
&profile.Description, &profile.CDNName, &profile.CDNID, 
&profile.RoutingDisabled, &profile.Type)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
fmt.Errorf("error: %w in creating profile:%s", err, profile.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "profile was created.")
+       w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/profiles?id=%d", 
inf.Version.Major, inf.Version.Minor, profile.ID))

Review Comment:
   Please use 
[`github.com/apache/trafficcontrol/lib/go-rfc.Location`](https://pkg.go.dev/github.com/apache/[email protected]+incompatible/lib/go-rfc#pkg-constants)
 instead of an inline string.



##########
traffic_ops/traffic_ops_golang/profile/profiles.go:
##########
@@ -360,3 +362,294 @@ type) VALUES (
 func deleteQuery() string {
        return `DELETE FROM profile WHERE id = :id`
 }
+
+// Read gets list of Profiles for APIv5
+func Read(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{
+               "cdn":   {Column: "c.id", Checker: api.IsInt},
+               "name":  {Column: "prof.name", Checker: nil},
+               "id":    {Column: "prof.id", Checker: api.IsInt},
+               "param": {Column: "pp.parameter", Checker: api.IsInt},
+       }
+
+       query := selectProfilesQuery()
+       if paramValue, ok := inf.Params["param"]; ok {
+               if len(paramValue) > 0 {
+                       query += " LEFT JOIN profile_parameter pp ON prof.id = 
pp.profile"
+               }
+       }
+
+       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 += where + orderBy + pagination
+       rows, err := tx.NamedQuery(query, queryValues)
+       if err != nil {
+               api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, 
fmt.Errorf("profile read: error getting profile(s): %w", err))
+               return
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       profile := tc.ProfileV5{}
+       var profileList []tc.ProfileV5
+       for rows.Next() {
+               if err = rows.Scan(&profile.Description, &profile.ID, 
&profile.LastUpdated, &profile.Name, &profile.RoutingDisabled, &profile.Type, 
&profile.CDNID, &profile.CDNName); err != nil {
+                       api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("error getting profile(s): %w", 
err))
+                       return
+               }
+               profileList = append(profileList, profile)
+       }
+       rows.Close()
+       profileInterfaces := []interface{}{}
+       for _, p := range profileList {
+               // Attach Parameters if the 'param' parameter is sent
+               if _, ok := inf.Params["param"]; ok {
+                       p.Parameters, err = ReadParameters(inf.Tx, inf.User, 
&p.ID)
+                       if err != nil {
+                               api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("profile read: error reading 
parameters for a profile: %w", err))
+                               return
+                       }
+               }
+               profileInterfaces = append(profileInterfaces, p)
+       }
+
+       api.WriteResp(w, r, profileInterfaces)
+       return
+}
+
+// Create a Profile for APIv5
+func Create(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
+
+       profile, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       //check if user can modify.
+       if len(strings.TrimSpace(profile.CDNName)) != 0 || profile.CDNID != 0 {
+               userErr, sysErr, statusCode := 
canProfileBeAlteredByCurrentUser(inf.User.UserName, inf.Tx.Tx, 
&profile.CDNName, &profile.CDNID)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+                       return
+               }
+       }
+
+       // check if profile already exists
+       var count int
+       err := tx.QueryRow("SELECT count(*) from profile where name=$1", 
profile.Name).Scan(&count)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("error: %w, when checking if profile '%s' exists", err, 
profile.Name))
+               return
+       }
+       if count == 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("profile:'%s' already exists", profile.Name), nil)
+               return
+       }
+
+       // create profile
+       query := `INSERT INTO profile (name, cdn, type, routing_disabled, 
description) 
+       VALUES ($1, $2, $3, $4, $5) 
+       RETURNING id, last_updated, name, description, (select name FROM cdn 
where id = $2), cdn, routing_disabled, type`
+
+       err = tx.QueryRow(query, profile.Name, profile.CDNID, profile.Type, 
profile.RoutingDisabled, profile.Description).
+               Scan(&profile.ID, &profile.LastUpdated, &profile.Name, 
&profile.Description, &profile.CDNName, &profile.CDNID, 
&profile.RoutingDisabled, &profile.Type)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
fmt.Errorf("error: %w in creating profile:%s", err, profile.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "profile was created.")
+       w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/profiles?id=%d", 
inf.Version.Major, inf.Version.Minor, profile.ID))
+       api.WriteAlertsObj(w, r, http.StatusCreated, alerts, profile)
+       return
+}
+
+// Update a profile for APIv5
+func Update(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, 
[]string{"id"})
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       tx := inf.Tx.Tx
+       profile, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       //check if user can modify.
+       if len(strings.TrimSpace(profile.CDNName)) != 0 || profile.CDNID != 0 {
+               userErr, sysErr, statusCode := 
canProfileBeAlteredByCurrentUser(inf.User.UserName, inf.Tx.Tx, 
&profile.CDNName, &profile.CDNID)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, tx, statusCode, userErr, sysErr)
+                       return
+               }
+       }
+
+       requestedProfileId := inf.IntParams["id"]
+       // check if the entity was already updated
+       userErr, sysErr, errCode = api.CheckIfUnModified(r.Header, inf.Tx, 
requestedProfileId, "profile")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       //update profile
+       query := `UPDATE profile SET
+               name = $2, 
+               cdn = $3,
+               type = $4, 
+               routing_disabled = $5, 
+               description = $6
+       WHERE id = $1
+       RETURNING id, last_updated, name, description, (select name FROM cdn 
where id = $3), cdn, routing_disabled, type`
+
+       err := tx.QueryRow(query, requestedProfileId, profile.Name, 
profile.CDNID, profile.Type, profile.RoutingDisabled, profile.Description).
+               Scan(&profile.ID, &profile.LastUpdated, &profile.Name, 
&profile.Description, &profile.CDNName, &profile.CDNID, 
&profile.RoutingDisabled, &profile.Type)
+
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("profile: %s not found", profile.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "profile was updated")
+       api.WriteAlertsObj(w, r, http.StatusOK, alerts, profile)
+       return
+}
+
+// Delete an profile for APIv5
+func Delete(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       id := inf.Params["id"]

Review Comment:
   This should be a required parameter as passed into `api.NewInfo`. If that is 
done, then it can also be specified as an integral parameter, which will allow 
you to access it as `inf.IntParams["id"]`, meaning you can skip the 
`strconv.Atoi` step.



-- 
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]

Reply via email to