From 7a70a0341e55fe1e3a13266e258d528ffbbc1784 Mon Sep 17 00:00:00 2001 From: knordstrom Date: Wed, 15 Jul 2026 12:38:21 -0400 Subject: [PATCH 1/3] Implement network patch --- api.yaml | 65 +++++ api/directory.gen.go | 257 +++++++++++++++----- api/networks.go | 78 ++++++ db/query.sql.go | 42 ++++ descriptors/ModuleDescriptor-template.json | 11 + query.sql | 11 + test/apifixtures/network.patch.refetch.json | 5 + test/apifixtures/network.patch.req.json | 4 + test/networks_test.go | 66 +++++ 9 files changed, 479 insertions(+), 60 deletions(-) create mode 100644 test/apifixtures/network.patch.refetch.json create mode 100644 test/apifixtures/network.patch.req.json diff --git a/api.yaml b/api.yaml index 44cb2f5..4f3096d 100644 --- a/api.yaml +++ b/api.yaml @@ -732,6 +732,52 @@ paths: text/plain: schema: type: string + patch: + summary: Update a Network + description: Update mutable fields of a Network + operationId: updateNetwork + parameters: + - name: id + in: path + description: ID of Network to update + required: true + schema: + type: string + format: uuid + requestBody: + description: Updated fields + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkPatch' + responses: + '204': + description: Network updated + '400': + description: Bad request + content: + text/plain: + schema: + type: string + '401': + description: Access denied + content: + text/plain: + schema: + type: string + '404': + description: Not found + content: + text/plain: + schema: + type: string + '500': + description: Internal server error + content: + text/plain: + schema: + type: string delete: summary: Delete Network by ID operationId: deleteNetwork @@ -1853,6 +1899,25 @@ components: type: number format: double default: 0.0 + NetworkPatch: + type: object + properties: + id: + type: string + format: uuid + nullable: true + description: Unique identifier; cannot be changed + consortium: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + priority: + type: number + format: double + nullable: true Tier: type: object diff --git a/api/directory.gen.go b/api/directory.gen.go index 641be2c..227e1d4 100644 --- a/api/directory.gen.go +++ b/api/directory.gen.go @@ -432,6 +432,16 @@ type Network struct { Priority float64 `json:"priority"` } +// NetworkPatch defines model for NetworkPatch. +type NetworkPatch struct { + Consortium nullable.Nullable[openapi_types.UUID] `json:"consortium,omitempty"` + + // Id Unique identifier; cannot be changed + Id nullable.Nullable[openapi_types.UUID] `json:"id,omitempty"` + Name nullable.Nullable[string] `json:"name,omitempty"` + Priority nullable.Nullable[float64] `json:"priority,omitempty"` +} + // NetworksResponse defines model for NetworksResponse. type NetworksResponse struct { About About `json:"about"` @@ -660,6 +670,9 @@ type AddEntryTierJSONRequestBody = EntryTier // AddNetworkJSONRequestBody defines body for AddNetwork for application/json ContentType. type AddNetworkJSONRequestBody = Network +// UpdateNetworkJSONRequestBody defines body for UpdateNetwork for application/json ContentType. +type UpdateNetworkJSONRequestBody = NetworkPatch + // AddTierJSONRequestBody defines body for AddTier for application/json ContentType. type AddTierJSONRequestBody = Tier @@ -749,6 +762,9 @@ type ServerInterface interface { // Returns a Network by ID // (GET /networks/{id}) GetNetwork(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) + // Update a Network + // (PATCH /networks/{id}) + UpdateNetwork(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) // Get all tiers // (GET /tiers) GetTiers(w http.ResponseWriter, r *http.Request, params GetTiersParams) @@ -1606,6 +1622,31 @@ func (siw *ServerInterfaceWrapper) GetNetwork(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r) } +// UpdateNetwork operation middleware +func (siw *ServerInterfaceWrapper) UpdateNetwork(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateNetwork(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetTiers operation middleware func (siw *ServerInterfaceWrapper) GetTiers(w http.ResponseWriter, r *http.Request) { @@ -1861,6 +1902,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc("POST "+options.BaseURL+"/networks", wrapper.AddNetwork) m.HandleFunc("DELETE "+options.BaseURL+"/networks/{id}", wrapper.DeleteNetwork) m.HandleFunc("GET "+options.BaseURL+"/networks/{id}", wrapper.GetNetwork) + m.HandleFunc("PATCH "+options.BaseURL+"/networks/{id}", wrapper.UpdateNetwork) m.HandleFunc("GET "+options.BaseURL+"/tiers", wrapper.GetTiers) m.HandleFunc("POST "+options.BaseURL+"/tiers", wrapper.AddTier) m.HandleFunc("DELETE "+options.BaseURL+"/tiers/{id}", wrapper.DeleteTier) @@ -3321,6 +3363,63 @@ func (response GetNetwork500TextResponse) VisitGetNetworkResponse(w http.Respons return err } +type UpdateNetworkRequestObject struct { + Id openapi_types.UUID `json:"id"` + Body *UpdateNetworkJSONRequestBody +} + +type UpdateNetworkResponseObject interface { + VisitUpdateNetworkResponse(w http.ResponseWriter) error +} + +type UpdateNetwork204Response struct { +} + +func (response UpdateNetwork204Response) VisitUpdateNetworkResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type UpdateNetwork400TextResponse string + +func (response UpdateNetwork400TextResponse) VisitUpdateNetworkResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateNetwork401TextResponse string + +func (response UpdateNetwork401TextResponse) VisitUpdateNetworkResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(401) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateNetwork404TextResponse string + +func (response UpdateNetwork404TextResponse) VisitUpdateNetworkResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(404) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateNetwork500TextResponse string + +func (response UpdateNetwork500TextResponse) VisitUpdateNetworkResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(500) + + _, err := w.Write([]byte(response)) + return err +} + type GetTiersRequestObject struct { Params GetTiersParams } @@ -3604,6 +3703,9 @@ type StrictServerInterface interface { // Returns a Network by ID // (GET /networks/{id}) GetNetwork(ctx context.Context, request GetNetworkRequestObject) (GetNetworkResponseObject, error) + // Update a Network + // (PATCH /networks/{id}) + UpdateNetwork(ctx context.Context, request UpdateNetworkRequestObject) (UpdateNetworkResponseObject, error) // Get all tiers // (GET /tiers) GetTiers(ctx context.Context, request GetTiersRequestObject) (GetTiersResponseObject, error) @@ -4439,6 +4541,39 @@ func (sh *strictHandler) GetNetwork(w http.ResponseWriter, r *http.Request, id o } } +// UpdateNetwork operation middleware +func (sh *strictHandler) UpdateNetwork(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + var request UpdateNetworkRequestObject + + request.Id = id + + var body UpdateNetworkJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UpdateNetwork(ctx, request.(UpdateNetworkRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateNetwork") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UpdateNetworkResponseObject); ok { + if err := validResponse.VisitUpdateNetworkResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetTiers operation middleware func (sh *strictHandler) GetTiers(w http.ResponseWriter, r *http.Request, params GetTiersParams) { var request GetTiersRequestObject @@ -4551,66 +4686,68 @@ func (sh *strictHandler) GetTier(w http.ResponseWriter, r *http.Request, id open // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "bvOfAAAA///Jms+vNZEAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/networks.go b/api/networks.go index b5ed12b..fae4820 100644 --- a/api/networks.go +++ b/api/networks.go @@ -161,3 +161,81 @@ func (a ApiImpl) DeleteNetwork(ctx context.Context, request DeleteNetworkRequest return DeleteNetwork204Response{}, nil } + +func (a ApiImpl) UpdateNetwork(ctx context.Context, request UpdateNetworkRequestObject) (UpdateNetworkResponseObject, error) { + authData := auth.GetAuthData(ctx) + + if !authData.HasRole(auth.ConsortialAdminRole) { + slog.ErrorContext(ctx, "permission denied") + return UpdateNetwork401TextResponse("Access denied"), nil + } + + if request.Body == nil { + return UpdateNetwork400TextResponse("You must provide fields to update"), nil + } + if request.Body.Id.IsSpecified() { + return UpdateNetwork400TextResponse("'id' cannot be changed"), nil + } + if request.Body.Consortium.IsSpecified() && request.Body.Consortium.IsNull() { + return UpdateNetwork400TextResponse("'consortium' cannot be set to null"), nil + } + if request.Body.Priority.IsSpecified() && request.Body.Priority.IsNull() { + return UpdateNetwork400TextResponse("'priority' cannot be set to null"), nil + } + + tx, err := a.pool.Begin(ctx) + if err != nil { + slog.ErrorContext(ctx, "failed to begin transaction", "error", err, "operation", "UpdateNetwork") + return UpdateNetwork500TextResponse("Internal server error"), nil + } + defer func() { _ = tx.Rollback(ctx) }() + qtx := a.queries.WithTx(tx) + + orig, err := qtx.GetNetworkByIdForUpdate(ctx, request.Id) + if errors.Is(err, pgx.ErrNoRows) { + return UpdateNetwork404TextResponse("Network not found"), nil + } else if err != nil { + slog.ErrorContext(ctx, "failed to get network for update", "error", err, "id", request.Id) + return UpdateNetwork500TextResponse("Internal server error"), nil + } + + consortiumID := orig.Consortium + if request.Body.Consortium.IsSpecified() { + consortiumID = request.Body.Consortium.MustGet() + } + if consortiumID != orig.Consortium { + consortium, err := qtx.EntryByIdForUpdate(ctx, consortiumID) + if errors.Is(err, pgx.ErrNoRows) { + return UpdateNetwork400TextResponse("Consortium not found"), nil + } else if err != nil { + slog.ErrorContext(ctx, "failed to get consortium", "error", err, "consortium", consortiumID) + return UpdateNetwork500TextResponse("Internal server error"), nil + } + if consortium.Type != string(EntryTypeConsortium) { + return UpdateNetwork400TextResponse("Entry is not a consortium"), nil + } + } + + priority := orig.Priority + if request.Body.Priority.IsSpecified() { + priority = request.Body.Priority.MustGet() + } + + err = qtx.UpdateNetwork(ctx, db.UpdateNetworkParams{ + ID: request.Id, + Consortium: consortiumID, + Name: maybeUpdateCol(orig.Name, request.Body.Name), + Priority: priority, + }) + if err != nil { + slog.ErrorContext(ctx, "failed to update network", "error", err, "id", request.Id) + return UpdateNetwork500TextResponse("Internal server error"), nil + } + + if err := tx.Commit(ctx); err != nil { + slog.ErrorContext(ctx, "failed to commit transaction", "error", err, "operation", "UpdateNetwork") + return UpdateNetwork500TextResponse("Internal server error"), nil + } + + return UpdateNetwork204Response{}, nil +} diff --git a/db/query.sql.go b/db/query.sql.go index fdbbe63..0a397d6 100644 --- a/db/query.sql.go +++ b/db/query.sql.go @@ -707,6 +707,22 @@ func (q *Queries) GetNetworkById(ctx context.Context, id uuid.UUID) (Network, er return i, err } +const getNetworkByIdForUpdate = `-- name: GetNetworkByIdForUpdate :one +SELECT id, consortium, name, priority FROM networks WHERE id = $1 LIMIT 1 FOR UPDATE +` + +func (q *Queries) GetNetworkByIdForUpdate(ctx context.Context, id uuid.UUID) (Network, error) { + row := q.db.QueryRow(ctx, getNetworkByIdForUpdate, id) + var i Network + err := row.Scan( + &i.ID, + &i.Consortium, + &i.Name, + &i.Priority, + ) + return i, err +} + const getTierById = `-- name: GetTierById :one SELECT id, consortium, name, level, type, cost FROM tiers WHERE id = $1 LIMIT 1 ` @@ -1173,6 +1189,32 @@ func (q *Queries) UpdateEntry(ctx context.Context, arg UpdateEntryParams) error return err } +const updateNetwork = `-- name: UpdateNetwork :exec +UPDATE networks +SET + consortium = $1, + name = $2, + priority = $3 +WHERE id = $4 +` + +type UpdateNetworkParams struct { + Consortium uuid.UUID + Name *string + Priority float64 + ID uuid.UUID +} + +func (q *Queries) UpdateNetwork(ctx context.Context, arg UpdateNetworkParams) error { + _, err := q.db.Exec(ctx, updateNetwork, + arg.Consortium, + arg.Name, + arg.Priority, + arg.ID, + ) + return err +} + const upsertAddress = `-- name: UpsertAddress :one INSERT INTO addresses ( id, entry, type diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 4624c60..5326bd9 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -216,6 +216,17 @@ "directory.public.all" ] }, + { + "methods": ["PATCH"], + "pathPattern": "/rsdir/networks/{id}", + "permissionsRequired" : [], + "permissionsDesired": [ + "directory.consortium.all", + "directory.institution.all", + "directory.system.all", + "directory.public.all" + ] + }, { "methods": ["DELETE"], "pathPattern": "/rsdir/networks/{id}", diff --git a/query.sql b/query.sql index f0f93f9..bfb4f20 100644 --- a/query.sql +++ b/query.sql @@ -153,6 +153,14 @@ INSERT INTO networks ( ) RETURNING *; +-- name: UpdateNetwork :exec +UPDATE networks +SET + consortium = @consortium, + name = @name, + priority = @priority +WHERE id = @id; + -- name: CreateEntryNetwork :one INSERT INTO entry_networks ( @@ -233,6 +241,9 @@ RETURNING *; -- name: GetNetworkById :one SELECT * FROM networks WHERE id = $1 LIMIT 1; +-- name: GetNetworkByIdForUpdate :one +SELECT * FROM networks WHERE id = $1 LIMIT 1 FOR UPDATE; + -- name: GetTierById :one SELECT * FROM tiers WHERE id = $1 LIMIT 1; diff --git a/test/apifixtures/network.patch.refetch.json b/test/apifixtures/network.patch.refetch.json new file mode 100644 index 0000000..61420f2 --- /dev/null +++ b/test/apifixtures/network.patch.refetch.json @@ -0,0 +1,5 @@ +{ + "id": "20000000-0000-0000-0000-000000000001", + "consortium": "00000000-0000-0000-0000-000000000004", + "priority": 1.5 +} diff --git a/test/apifixtures/network.patch.req.json b/test/apifixtures/network.patch.req.json new file mode 100644 index 0000000..b2b8408 --- /dev/null +++ b/test/apifixtures/network.patch.req.json @@ -0,0 +1,4 @@ +{ + "name": null, + "priority": 1.5 +} diff --git a/test/networks_test.go b/test/networks_test.go index 0bb2e6a..de07a40 100644 --- a/test/networks_test.go +++ b/test/networks_test.go @@ -59,6 +59,72 @@ func TestNetworkCases(t *testing.T) { body: `{"name":"Institution Network","consortium":"00000000-0000-0000-0000-000000000002"}`, addlHeaders: consortiumPermissionHeaders, }, + { + name: "PATCH network", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusNoContent, + bodyFile: "network.patch.req.json", + refetchFile: "network.patch.refetch.json", + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network cannot change id", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"id":"20000000-0000-0000-0000-000000000003","priority":99}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network with no fields", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusNoContent, + body: `{}`, + refetchFile: "network.patch.refetch.json", + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network rejects null consortium", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"consortium":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network rejects null priority", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"priority":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network rejects non-consortium entry", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"consortium":"00000000-0000-0000-0000-000000000002"}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network not found", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000099", + status: http.StatusNotFound, + body: `{}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH network requires consortial admin", + method: http.MethodPatch, + endpoint: "/networks/20000000-0000-0000-0000-000000000001", + status: http.StatusUnauthorized, + body: `{"priority":2}`, + addlHeaders: institutionPermissionHeaders, + }, { name: "DELETE network", method: http.MethodDelete, From 401fa8bda2784b44d61b2f85cf089761aedbd3ca Mon Sep 17 00:00:00 2001 From: Kurt Nordstrom Date: Wed, 15 Jul 2026 15:43:20 -0400 Subject: [PATCH 2/3] Fix test error. Implement PATCH for tiers --- api.yaml | 101 ++++++++ api/directory.gen.go | 285 ++++++++++++++++----- api/tiers.go | 106 ++++++++ db/query.sql.go | 50 ++++ descriptors/ModuleDescriptor-template.json | 11 + query.sql | 13 + test/apifixtures/tier.patch.refetch.json | 7 + test/apifixtures/tier.patch.req.json | 6 + test/networks_test.go | 2 +- test/tiers_test.go | 98 +++++++ 10 files changed, 616 insertions(+), 63 deletions(-) create mode 100644 test/apifixtures/tier.patch.refetch.json create mode 100644 test/apifixtures/tier.patch.req.json diff --git a/api.yaml b/api.yaml index 4f3096d..03737e2 100644 --- a/api.yaml +++ b/api.yaml @@ -572,6 +572,52 @@ paths: text/plain: schema: type: string + patch: + summary: Update a Tier + description: Update mutable fields of a Tier + operationId: updateTier + parameters: + - name: id + in: path + description: ID of Tier to update + required: true + schema: + type: string + format: uuid + requestBody: + description: Updated fields + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TierPatch' + responses: + '204': + description: Tier updated + '400': + description: Bad request + content: + text/plain: + schema: + type: string + '401': + description: Access denied + content: + text/plain: + schema: + type: string + '404': + description: Not found + content: + text/plain: + schema: + type: string + '500': + description: Internal server error + content: + text/plain: + schema: + type: string delete: summary: Delete Tier by ID operationId: deleteTier @@ -1947,17 +1993,72 @@ components: - secondarymail - standard - urgent + x-enum-varnames: + - Express + - Normal + - Rush + - Secondarymail + - Standard + - Urgent default: standard type: type: string enum: - loan - copy + x-enum-varnames: + - Loan + - Copy default: loan cost: type: number format: double default: 0.0 + TierPatch: + type: object + properties: + id: + type: string + format: uuid + nullable: true + description: Unique identifier; cannot be changed + consortium: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + level: + type: string + nullable: true + enum: + - express + - normal + - rush + - secondarymail + - standard + - urgent + x-enum-varnames: + - TierPatchLevelExpress + - TierPatchLevelNormal + - TierPatchLevelRush + - TierPatchLevelSecondarymail + - TierPatchLevelStandard + - TierPatchLevelUrgent + type: + type: string + nullable: true + enum: + - loan + - copy + x-enum-varnames: + - TierPatchTypeLoan + - TierPatchTypeCopy + cost: + type: number + format: double + nullable: true EntryNetwork: type: object diff --git a/api/directory.gen.go b/api/directory.gen.go index 227e1d4..47fa9fc 100644 --- a/api/directory.gen.go +++ b/api/directory.gen.go @@ -88,6 +88,22 @@ const ( Loan TierType = "loan" ) +// Defines values for TierPatchLevel. +const ( + TierPatchLevelExpress TierPatchLevel = "express" + TierPatchLevelNormal TierPatchLevel = "normal" + TierPatchLevelRush TierPatchLevel = "rush" + TierPatchLevelSecondarymail TierPatchLevel = "secondarymail" + TierPatchLevelStandard TierPatchLevel = "standard" + TierPatchLevelUrgent TierPatchLevel = "urgent" +) + +// Defines values for TierPatchType. +const ( + TierPatchTypeCopy TierPatchType = "copy" + TierPatchTypeLoan TierPatchType = "loan" +) + // Defines values for EntryLookupKey. const ( EntryLookupKeyById EntryLookupKey = "by-id" @@ -526,6 +542,24 @@ type TierLevel string // TierType defines model for Tier.Type. type TierType string +// TierPatch defines model for TierPatch. +type TierPatch struct { + Consortium nullable.Nullable[openapi_types.UUID] `json:"consortium,omitempty"` + Cost nullable.Nullable[float64] `json:"cost,omitempty"` + + // Id Unique identifier; cannot be changed + Id nullable.Nullable[openapi_types.UUID] `json:"id,omitempty"` + Level nullable.Nullable[TierPatchLevel] `json:"level,omitempty"` + Name nullable.Nullable[string] `json:"name,omitempty"` + Type nullable.Nullable[TierPatchType] `json:"type,omitempty"` +} + +// TierPatchLevel defines model for TierPatch.Level. +type TierPatchLevel string + +// TierPatchType defines model for TierPatch.Type. +type TierPatchType string + // TiersResponse defines model for TiersResponse. type TiersResponse struct { About About `json:"about"` @@ -676,6 +710,9 @@ type UpdateNetworkJSONRequestBody = NetworkPatch // AddTierJSONRequestBody defines body for AddTier for application/json ContentType. type AddTierJSONRequestBody = Tier +// UpdateTierJSONRequestBody defines body for UpdateTier for application/json ContentType. +type UpdateTierJSONRequestBody = TierPatch + // ServerInterface represents all server handlers. type ServerInterface interface { // Get all Closures @@ -777,6 +814,9 @@ type ServerInterface interface { // Returns a Tier by ID // (GET /tiers/{id}) GetTier(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) + // Update a Tier + // (PATCH /tiers/{id}) + UpdateTier(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) } // ServerInterfaceWrapper converts contexts to parameters. @@ -1754,6 +1794,31 @@ func (siw *ServerInterfaceWrapper) GetTier(w http.ResponseWriter, r *http.Reques handler.ServeHTTP(w, r) } +// UpdateTier operation middleware +func (siw *ServerInterfaceWrapper) UpdateTier(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateTier(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + type UnescapedCookieParamError struct { ParamName string Err error @@ -1907,6 +1972,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc("POST "+options.BaseURL+"/tiers", wrapper.AddTier) m.HandleFunc("DELETE "+options.BaseURL+"/tiers/{id}", wrapper.DeleteTier) m.HandleFunc("GET "+options.BaseURL+"/tiers/{id}", wrapper.GetTier) + m.HandleFunc("PATCH "+options.BaseURL+"/tiers/{id}", wrapper.UpdateTier) return m } @@ -3617,6 +3683,63 @@ func (response GetTier500TextResponse) VisitGetTierResponse(w http.ResponseWrite return err } +type UpdateTierRequestObject struct { + Id openapi_types.UUID `json:"id"` + Body *UpdateTierJSONRequestBody +} + +type UpdateTierResponseObject interface { + VisitUpdateTierResponse(w http.ResponseWriter) error +} + +type UpdateTier204Response struct { +} + +func (response UpdateTier204Response) VisitUpdateTierResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type UpdateTier400TextResponse string + +func (response UpdateTier400TextResponse) VisitUpdateTierResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(400) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateTier401TextResponse string + +func (response UpdateTier401TextResponse) VisitUpdateTierResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(401) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateTier404TextResponse string + +func (response UpdateTier404TextResponse) VisitUpdateTierResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(404) + + _, err := w.Write([]byte(response)) + return err +} + +type UpdateTier500TextResponse string + +func (response UpdateTier500TextResponse) VisitUpdateTierResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(500) + + _, err := w.Write([]byte(response)) + return err +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { // Get all Closures @@ -3718,6 +3841,9 @@ type StrictServerInterface interface { // Returns a Tier by ID // (GET /tiers/{id}) GetTier(ctx context.Context, request GetTierRequestObject) (GetTierResponseObject, error) + // Update a Tier + // (PATCH /tiers/{id}) + UpdateTier(ctx context.Context, request UpdateTierRequestObject) (UpdateTierResponseObject, error) } type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc @@ -4683,71 +4809,106 @@ func (sh *strictHandler) GetTier(w http.ResponseWriter, r *http.Request, id open } } +// UpdateTier operation middleware +func (sh *strictHandler) UpdateTier(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + var request UpdateTierRequestObject + + request.Id = id + + var body UpdateTierJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UpdateTier(ctx, request.(UpdateTierRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateTier") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UpdateTierResponseObject); ok { + if err := validResponse.VisitUpdateTierResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "bvOfAAAA///Jms+vNZEAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/tiers.go b/api/tiers.go index 95dc78a..2118e4a 100644 --- a/api/tiers.go +++ b/api/tiers.go @@ -186,3 +186,109 @@ func (a ApiImpl) DeleteTier(ctx context.Context, request DeleteTierRequestObject return DeleteTier204Response{}, nil } + +func (a ApiImpl) UpdateTier(ctx context.Context, request UpdateTierRequestObject) (UpdateTierResponseObject, error) { + authData := auth.GetAuthData(ctx) + + if !authData.HasRole(auth.ConsortialAdminRole) { + slog.ErrorContext(ctx, "permission denied") + return UpdateTier401TextResponse("Access denied"), nil + } + + if request.Body == nil { + return UpdateTier400TextResponse("You must provide fields to update"), nil + } + if request.Body.Id.IsSpecified() { + return UpdateTier400TextResponse("'id' cannot be changed"), nil + } + if request.Body.Consortium.IsSpecified() && request.Body.Consortium.IsNull() { + return UpdateTier400TextResponse("'consortium' cannot be set to null"), nil + } + if request.Body.Level.IsSpecified() && request.Body.Level.IsNull() { + return UpdateTier400TextResponse("'level' cannot be set to null"), nil + } + if request.Body.Type.IsSpecified() && request.Body.Type.IsNull() { + return UpdateTier400TextResponse("'type' cannot be set to null"), nil + } + if request.Body.Cost.IsSpecified() && request.Body.Cost.IsNull() { + return UpdateTier400TextResponse("'cost' cannot be set to null"), nil + } + + tx, err := a.pool.Begin(ctx) + if err != nil { + slog.ErrorContext(ctx, "failed to begin transaction", "error", err, "operation", "UpdateTier") + return UpdateTier500TextResponse("Internal server error"), nil + } + defer func() { _ = tx.Rollback(ctx) }() + qtx := a.queries.WithTx(tx) + + orig, err := qtx.GetTierByIdForUpdate(ctx, request.Id) + if errors.Is(err, pgx.ErrNoRows) { + return UpdateTier404TextResponse("Tier not found"), nil + } else if err != nil { + slog.ErrorContext(ctx, "failed to get tier for update", "error", err, "id", request.Id) + return UpdateTier500TextResponse("Internal server error"), nil + } + + consortiumID := orig.Consortium + if request.Body.Consortium.IsSpecified() { + consortiumID = request.Body.Consortium.MustGet() + } + if consortiumID != orig.Consortium { + consortium, err := qtx.EntryByIdForUpdate(ctx, consortiumID) + if errors.Is(err, pgx.ErrNoRows) { + return UpdateTier400TextResponse("Consortium not found"), nil + } else if err != nil { + slog.ErrorContext(ctx, "failed to get consortium", "error", err, "consortium", consortiumID) + return UpdateTier500TextResponse("Internal server error"), nil + } + if consortium.Type != string(EntryTypeConsortium) { + return UpdateTier400TextResponse("Entry is not a consortium"), nil + } + } + + level := orig.Level + if request.Body.Level.IsSpecified() { + level = string(request.Body.Level.MustGet()) + } + switch TierLevel(level) { + case Express, Normal, Rush, Secondarymail, Standard, Urgent: + default: + return UpdateTier400TextResponse("Invalid tier level"), nil + } + + tierType := orig.Type + if request.Body.Type.IsSpecified() { + tierType = string(request.Body.Type.MustGet()) + } + switch TierType(tierType) { + case Loan, Copy: + default: + return UpdateTier400TextResponse("Invalid tier type"), nil + } + + cost := orig.Cost + if request.Body.Cost.IsSpecified() { + cost = request.Body.Cost.MustGet() + } + + err = qtx.UpdateTier(ctx, db.UpdateTierParams{ + ID: request.Id, + Consortium: consortiumID, + Name: maybeUpdateCol(orig.Name, request.Body.Name), + Level: level, + Type: tierType, + Cost: cost, + }) + if err != nil { + slog.ErrorContext(ctx, "failed to update tier", "error", err, "id", request.Id) + return UpdateTier500TextResponse("Internal server error"), nil + } + + if err := tx.Commit(ctx); err != nil { + slog.ErrorContext(ctx, "failed to commit transaction", "error", err, "operation", "UpdateTier") + return UpdateTier500TextResponse("Internal server error"), nil + } + + return UpdateTier204Response{}, nil +} diff --git a/db/query.sql.go b/db/query.sql.go index 0a397d6..ae59839 100644 --- a/db/query.sql.go +++ b/db/query.sql.go @@ -741,6 +741,24 @@ func (q *Queries) GetTierById(ctx context.Context, id uuid.UUID) (Tier, error) { return i, err } +const getTierByIdForUpdate = `-- name: GetTierByIdForUpdate :one +SELECT id, consortium, name, level, type, cost FROM tiers WHERE id = $1 LIMIT 1 FOR UPDATE +` + +func (q *Queries) GetTierByIdForUpdate(ctx context.Context, id uuid.UUID) (Tier, error) { + row := q.db.QueryRow(ctx, getTierByIdForUpdate, id) + var i Tier + err := row.Scan( + &i.ID, + &i.Consortium, + &i.Name, + &i.Level, + &i.Type, + &i.Cost, + ) + return i, err +} + const listClosures = `-- name: ListClosures :many SELECT id, entry, start_date, end_date, reason FROM closures LIMIT $2 @@ -1215,6 +1233,38 @@ func (q *Queries) UpdateNetwork(ctx context.Context, arg UpdateNetworkParams) er return err } +const updateTier = `-- name: UpdateTier :exec +UPDATE tiers +SET + consortium = $1, + name = $2, + level = $3, + type = $4, + cost = $5 +WHERE id = $6 +` + +type UpdateTierParams struct { + Consortium uuid.UUID + Name *string + Level string + Type string + Cost float64 + ID uuid.UUID +} + +func (q *Queries) UpdateTier(ctx context.Context, arg UpdateTierParams) error { + _, err := q.db.Exec(ctx, updateTier, + arg.Consortium, + arg.Name, + arg.Level, + arg.Type, + arg.Cost, + arg.ID, + ) + return err +} + const upsertAddress = `-- name: UpsertAddress :one INSERT INTO addresses ( id, entry, type diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 5326bd9..6911352 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -166,6 +166,17 @@ "directory.public.all" ] }, + { + "methods": ["PATCH"], + "pathPattern": "/rsdir/tiers/{id}", + "permissionsRequired" : [], + "permissionsDesired": [ + "directory.consortium.all", + "directory.institution.all", + "directory.system.all", + "directory.public.all" + ] + }, { "methods": ["DELETE"], "pathPattern": "/rsdir/tiers/{id}", diff --git a/query.sql b/query.sql index bfb4f20..569c656 100644 --- a/query.sql +++ b/query.sql @@ -161,6 +161,16 @@ SET priority = @priority WHERE id = @id; +-- name: UpdateTier :exec +UPDATE tiers +SET + consortium = @consortium, + name = @name, + level = @level, + type = @type, + cost = @cost +WHERE id = @id; + -- name: CreateEntryNetwork :one INSERT INTO entry_networks ( @@ -247,6 +257,9 @@ SELECT * FROM networks WHERE id = $1 LIMIT 1 FOR UPDATE; -- name: GetTierById :one SELECT * FROM tiers WHERE id = $1 LIMIT 1; +-- name: GetTierByIdForUpdate :one +SELECT * FROM tiers WHERE id = $1 LIMIT 1 FOR UPDATE; + -- name: GetEntryNetworkById :one SELECT * FROM entry_networks WHERE id = $1 LIMIT 1; diff --git a/test/apifixtures/tier.patch.refetch.json b/test/apifixtures/tier.patch.refetch.json new file mode 100644 index 0000000..db5e7d5 --- /dev/null +++ b/test/apifixtures/tier.patch.refetch.json @@ -0,0 +1,7 @@ +{ + "id": "30000000-0000-0000-0000-000000000001", + "consortium": "00000000-0000-0000-0000-000000000004", + "level": "urgent", + "type": "copy", + "cost": 1.5 +} diff --git a/test/apifixtures/tier.patch.req.json b/test/apifixtures/tier.patch.req.json new file mode 100644 index 0000000..dce540c --- /dev/null +++ b/test/apifixtures/tier.patch.req.json @@ -0,0 +1,6 @@ +{ + "name": null, + "level": "urgent", + "type": "copy", + "cost": 1.5 +} diff --git a/test/networks_test.go b/test/networks_test.go index de07a40..31229a8 100644 --- a/test/networks_test.go +++ b/test/networks_test.go @@ -82,7 +82,7 @@ func TestNetworkCases(t *testing.T) { endpoint: "/networks/20000000-0000-0000-0000-000000000001", status: http.StatusNoContent, body: `{}`, - refetchFile: "network.patch.refetch.json", + refetchFile: "network.get.res.json", addlHeaders: consortiumPermissionHeaders, }, { diff --git a/test/tiers_test.go b/test/tiers_test.go index f0a9209..804d21c 100644 --- a/test/tiers_test.go +++ b/test/tiers_test.go @@ -59,6 +59,104 @@ func TestTierCases(t *testing.T) { body: `{"name":"Institution Tier","consortium":"00000000-0000-0000-0000-000000000002","level":"standard","type":"loan"}`, addlHeaders: consortiumPermissionHeaders, }, + { + name: "PATCH tier", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusNoContent, + bodyFile: "tier.patch.req.json", + refetchFile: "tier.patch.refetch.json", + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier cannot change id", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"id":"30000000-0000-0000-0000-000000000003","cost":99}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier with no fields", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusNoContent, + body: `{}`, + refetchFile: "tier.get.res.json", + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects null consortium", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"consortium":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects null level", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"level":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects null type", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"type":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects null cost", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"cost":null}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects non-consortium entry", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"consortium":"00000000-0000-0000-0000-000000000002"}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects invalid level", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"level":"overnight"}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier rejects invalid type", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusBadRequest, + body: `{"type":"returnable"}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier not found", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000099", + status: http.StatusNotFound, + body: `{}`, + addlHeaders: consortiumPermissionHeaders, + }, + { + name: "PATCH tier requires consortial admin", + method: http.MethodPatch, + endpoint: "/tiers/30000000-0000-0000-0000-000000000001", + status: http.StatusUnauthorized, + body: `{"cost":2}`, + addlHeaders: institutionPermissionHeaders, + }, { name: "DELETE tier", method: http.MethodDelete, From 3b2a84a7fcd341a0db554cbd63d64046faca0fe7 Mon Sep 17 00:00:00 2001 From: Kurt Nordstrom Date: Wed, 15 Jul 2026 16:07:29 -0400 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- api/networks.go | 3 --- api/tiers.go | 3 --- 2 files changed, 6 deletions(-) diff --git a/api/networks.go b/api/networks.go index fae4820..a3bf26c 100644 --- a/api/networks.go +++ b/api/networks.go @@ -170,9 +170,6 @@ func (a ApiImpl) UpdateNetwork(ctx context.Context, request UpdateNetworkRequest return UpdateNetwork401TextResponse("Access denied"), nil } - if request.Body == nil { - return UpdateNetwork400TextResponse("You must provide fields to update"), nil - } if request.Body.Id.IsSpecified() { return UpdateNetwork400TextResponse("'id' cannot be changed"), nil } diff --git a/api/tiers.go b/api/tiers.go index 2118e4a..766eb53 100644 --- a/api/tiers.go +++ b/api/tiers.go @@ -195,9 +195,6 @@ func (a ApiImpl) UpdateTier(ctx context.Context, request UpdateTierRequestObject return UpdateTier401TextResponse("Access denied"), nil } - if request.Body == nil { - return UpdateTier400TextResponse("You must provide fields to update"), nil - } if request.Body.Id.IsSpecified() { return UpdateTier400TextResponse("'id' cannot be changed"), nil }