From d002e4ccf39f22978c932c77ce4fba1047e12a22 Mon Sep 17 00:00:00 2001 From: David Juhasz Date: Fri, 27 Sep 2024 16:48:15 -0700 Subject: [PATCH] Add total result count to package list API Refs #988 This commit connects the API "GET /package" endpoint to the persistence layer `ListPackages()` method. The cursor request parameter is replaced by the optional limit and offset parameters. The response now returns the current page limit and offset, and the total number of results found before paging, instead of a cursor value. These changes will break the existing cursor-based pagination in the Enduro Dashboard. I will fix the Dashboard paging in a subsequent commit. - Add optional `limit` and `offset` parameters to the GET /package request - Add page `limit`, `offset`, and `total` fields to GET /package response - Use `persistence.ListPackages()` to populate API results - Add an adapter to convert goa `package_.ListPayload` search filters to `persistence.Filter` filters - Add tests for the conversion of the package API parameters to persistence layer parameters, and the persistence search results to API response results --- internal/api/design/package_.go | 23 +- internal/api/design/pagination.go | 4 +- internal/api/gen/http/cli/enduro/cli.go | 12 +- internal/api/gen/http/openapi.json | 67 ++++- internal/api/gen/http/openapi.yaml | 54 +++- internal/api/gen/http/openapi3.json | 135 ++++++--- internal/api/gen/http/openapi3.yaml | 105 +++++-- internal/api/gen/http/package_/client/cli.go | 29 +- .../gen/http/package_/client/encode_decode.go | 48 +++- .../api/gen/http/package_/client/types.go | 59 ++-- .../gen/http/package_/server/encode_decode.go | 63 ++++- .../api/gen/http/package_/server/types.go | 34 ++- internal/api/gen/package_/client.go | 4 +- internal/api/gen/package_/endpoints.go | 7 +- internal/api/gen/package_/service.go | 116 +++++++- internal/api/gen/package_/views/view.go | 116 ++++++++ internal/datatypes/package_.go | 6 +- internal/package_/convert.go | 96 +++++++ internal/package_/goa.go | 95 +------ internal/package_/goa_test.go | 266 ++++++++++++++++++ internal/persistence/ent/client/package.go | 16 +- .../persistence/ent/client/package_test.go | 29 +- internal/persistence/fake/mock_persistence.go | 6 +- internal/persistence/filter.go | 13 + internal/persistence/persistence.go | 2 +- internal/persistence/telemetry.go | 2 +- 26 files changed, 1119 insertions(+), 288 deletions(-) diff --git a/internal/api/design/package_.go b/internal/api/design/package_.go index 7f589c977..255fb3a5f 100644 --- a/internal/api/design/package_.go +++ b/internal/api/design/package_.go @@ -78,10 +78,12 @@ var _ = Service("package", func() { Attribute("status", String, func() { EnumPackageStatus() }) - Attribute("cursor", String, "Pagination cursor") + Attribute("limit", Int, "Limit number of results to return") + Attribute("offset", Int, "Offset from the beginning of the found set") + Token("token", String) }) - Result(PaginatedCollectionOf(StoredPackage)) + Result(PackageList) HTTP(func() { GET("/") Response(StatusOK) @@ -92,7 +94,8 @@ var _ = Service("package", func() { Param("latest_created_time") Param("location_id") Param("status") - Param("cursor") + Param("limit") + Param("offset") }) }) }) @@ -319,6 +322,20 @@ var StoredPackage = ResultType("application/vnd.enduro.stored-package", func() { Required("id", "status", "created_at") }) +var Page = ResultType("application/vnd.enduro.page", func() { + Description("Page represents a subset of search results.") + Attribute("limit", Int, "Maximum items per page") + Attribute("offset", Int, "Offset from first result to start of page") + Attribute("total", Int, "Total result count before paging") + Required("limit", "offset", "total") +}) + +var PackageList = ResultType("application/vnd.enduro.packages", func() { + Attribute("items", CollectionOf(StoredPackage)) + Attribute("page", Page) + Required("items") +}) + var PackageNotFound = Type("PackageNotFound", func() { Description("Package not found.") TypeName("PackageNotFound") diff --git a/internal/api/design/pagination.go b/internal/api/design/pagination.go index 472cac743..7028d73c7 100644 --- a/internal/api/design/pagination.go +++ b/internal/api/design/pagination.go @@ -1,6 +1,8 @@ package design -import "goa.design/goa/v3/dsl" +import ( + "goa.design/goa/v3/dsl" +) func PaginatedCollectionOf(v interface{}, adsl ...func()) interface{} { return func() { diff --git a/internal/api/gen/http/cli/enduro/cli.go b/internal/api/gen/http/cli/enduro/cli.go index cb15f7497..b158b9faf 100644 --- a/internal/api/gen/http/cli/enduro/cli.go +++ b/internal/api/gen/http/cli/enduro/cli.go @@ -69,7 +69,8 @@ func ParseEndpoint( package_ListLatestCreatedTimeFlag = package_ListFlags.String("latest-created-time", "", "") package_ListLocationIDFlag = package_ListFlags.String("location-id", "", "") package_ListStatusFlag = package_ListFlags.String("status", "", "") - package_ListCursorFlag = package_ListFlags.String("cursor", "", "") + package_ListLimitFlag = package_ListFlags.String("limit", "", "") + package_ListOffsetFlag = package_ListFlags.String("offset", "", "") package_ListTokenFlag = package_ListFlags.String("token", "", "") package_ShowFlags = flag.NewFlagSet("show", flag.ExitOnError) @@ -319,7 +320,7 @@ func ParseEndpoint( data, err = package_c.BuildMonitorPayload(*package_MonitorTicketFlag) case "list": endpoint = c.List() - data, err = package_c.BuildListPayload(*package_ListNameFlag, *package_ListAipIDFlag, *package_ListEarliestCreatedTimeFlag, *package_ListLatestCreatedTimeFlag, *package_ListLocationIDFlag, *package_ListStatusFlag, *package_ListCursorFlag, *package_ListTokenFlag) + data, err = package_c.BuildListPayload(*package_ListNameFlag, *package_ListAipIDFlag, *package_ListEarliestCreatedTimeFlag, *package_ListLatestCreatedTimeFlag, *package_ListLocationIDFlag, *package_ListStatusFlag, *package_ListLimitFlag, *package_ListOffsetFlag, *package_ListTokenFlag) case "show": endpoint = c.Show() data, err = package_c.BuildShowPayload(*package_ShowIDFlag, *package_ShowTokenFlag) @@ -439,7 +440,7 @@ Example: } func package_ListUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] package list -name STRING -aip-id STRING -earliest-created-time STRING -latest-created-time STRING -location-id STRING -status STRING -cursor STRING -token STRING + fmt.Fprintf(os.Stderr, `%[1]s [flags] package list -name STRING -aip-id STRING -earliest-created-time STRING -latest-created-time STRING -location-id STRING -status STRING -limit INT -offset INT -token STRING List all stored packages -name STRING: @@ -448,11 +449,12 @@ List all stored packages -latest-created-time STRING: -location-id STRING: -status STRING: - -cursor STRING: + -limit INT: + -offset INT: -token STRING: Example: - %[1]s package list --name "abc123" --aip-id "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" --earliest-created-time "1970-01-01T00:00:01Z" --latest-created-time "1970-01-01T00:00:01Z" --location-id "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" --status "in progress" --cursor "abc123" --token "abc123" + %[1]s package list --name "abc123" --aip-id "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" --earliest-created-time "1970-01-01T00:00:01Z" --latest-created-time "1970-01-01T00:00:01Z" --location-id "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" --status "in progress" --limit 1 --offset 1 --token "abc123" `, os.Args[0]) } diff --git a/internal/api/gen/http/openapi.json b/internal/api/gen/http/openapi.json index 9145a267d..c457991aa 100644 --- a/internal/api/gen/http/openapi.json +++ b/internal/api/gen/http/openapi.json @@ -206,6 +206,41 @@ "title": "Mediatype identifier: application/vnd.enduro.package-preservation-task; type=collection; view=default", "type": "array" }, + "EnduroPageResponseBody": { + "description": "Page represents a subset of search results. (default view)", + "example": { + "limit": 1, + "offset": 1, + "total": 1 + }, + "properties": { + "limit": { + "description": "Maximum items per page", + "example": 1, + "format": "int64", + "type": "integer" + }, + "offset": { + "description": "Offset from first result to start of page", + "example": 1, + "format": "int64", + "type": "integer" + }, + "total": { + "description": "Total result count before paging", + "example": 1, + "format": "int64", + "type": "integer" + } + }, + "required": [ + "limit", + "offset", + "total" + ], + "title": "Mediatype identifier: application/vnd.enduro.page; view=default", + "type": "object" + }, "EnduroStoredPackageResponseBody": { "description": "StoredPackage describes a package retrieved by the service. (default view)", "example": { @@ -534,6 +569,7 @@ "type": "object" }, "PackageListResponseBody": { + "description": "ListResponseBody result type (default view)", "example": { "items": [ { @@ -549,21 +585,24 @@ "workflow_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" } ], - "next_cursor": "abc123" + "page": { + "limit": 1, + "offset": 1, + "total": 1 + } }, "properties": { "items": { "$ref": "#/definitions/EnduroStoredPackageResponseBodyCollection" }, - "next_cursor": { - "example": "abc123", - "type": "string" + "page": { + "$ref": "#/definitions/EnduroPageResponseBody" } }, "required": [ "items" ], - "title": "PackageListResponseBody", + "title": "Mediatype identifier: application/vnd.enduro.packages; view=default", "type": "object" }, "PackageMonitorNotAvailableResponseBody": { @@ -2843,11 +2882,18 @@ "type": "string" }, { - "description": "Pagination cursor", + "description": "Limit number of results to return", "in": "query", - "name": "cursor", + "name": "limit", "required": false, - "type": "string" + "type": "integer" + }, + { + "description": "Offset from the beginning of the found set", + "in": "query", + "name": "offset", + "required": false, + "type": "integer" }, { "in": "header", @@ -2860,10 +2906,7 @@ "200": { "description": "OK response.", "schema": { - "$ref": "#/definitions/PackageListResponseBody", - "required": [ - "items" - ] + "$ref": "#/definitions/PackageListResponseBody" } }, "401": { diff --git a/internal/api/gen/http/openapi.yaml b/internal/api/gen/http/openapi.yaml index 8786cb912..3ad8c20e4 100644 --- a/internal/api/gen/http/openapi.yaml +++ b/internal/api/gen/http/openapi.yaml @@ -61,11 +61,16 @@ paths: - queued - abandoned - pending - - name: cursor + - name: limit in: query - description: Pagination cursor + description: Limit number of results to return required: false - type: string + type: integer + - name: offset + in: query + description: Offset from the beginning of the found set + required: false + type: integer - name: Authorization in: header required: false @@ -75,8 +80,6 @@ paths: description: OK response. schema: $ref: '#/definitions/PackageListResponseBody' - required: - - items "401": description: Unauthorized response. schema: @@ -1264,6 +1267,34 @@ definitions: started_at: "1970-01-01T00:00:01Z" status: in progress task_id: abc123 + EnduroPageResponseBody: + title: 'Mediatype identifier: application/vnd.enduro.page; view=default' + type: object + properties: + limit: + type: integer + description: Maximum items per page + example: 1 + format: int64 + offset: + type: integer + description: Offset from first result to start of page + example: 1 + format: int64 + total: + type: integer + description: Total result count before paging + example: 1 + format: int64 + description: Page represents a subset of search results. (default view) + example: + limit: 1 + offset: 1 + total: 1 + required: + - limit + - offset + - total EnduroStoredPackageResponseBody: title: 'Mediatype identifier: application/vnd.enduro.stored-package; view=default' type: object @@ -1529,14 +1560,14 @@ definitions: required: - location_id PackageListResponseBody: - title: PackageListResponseBody + title: 'Mediatype identifier: application/vnd.enduro.packages; view=default' type: object properties: items: $ref: '#/definitions/EnduroStoredPackageResponseBodyCollection' - next_cursor: - type: string - example: abc123 + page: + $ref: '#/definitions/EnduroPageResponseBody' + description: ListResponseBody result type (default view) example: items: - aip_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 @@ -1549,7 +1580,10 @@ definitions: started_at: "1970-01-01T00:00:01Z" status: in progress workflow_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - next_cursor: abc123 + page: + limit: 1 + offset: 1 + total: 1 required: - items PackageMonitorNotAvailableResponseBody: diff --git a/internal/api/gen/http/openapi3.json b/internal/api/gen/http/openapi3.json index 921e6ae30..073ee6245 100644 --- a/internal/api/gen/http/openapi3.json +++ b/internal/api/gen/http/openapi3.json @@ -389,6 +389,75 @@ }, "type": "array" }, + "EnduroPackages": { + "example": { + "items": [ + { + "aip_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", + "completed_at": "1970-01-01T00:00:01Z", + "created_at": "1970-01-01T00:00:01Z", + "id": 1, + "location_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", + "name": "abc123", + "run_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", + "started_at": "1970-01-01T00:00:01Z", + "status": "in progress", + "workflow_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" + } + ], + "page": { + "limit": 1, + "offset": 1, + "total": 1 + } + }, + "properties": { + "items": { + "$ref": "#/components/schemas/EnduroStoredPackageCollection" + }, + "page": { + "$ref": "#/components/schemas/EnduroPage" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "EnduroPage": { + "description": "Page represents a subset of search results.", + "example": { + "limit": 1, + "offset": 1, + "total": 1 + }, + "properties": { + "limit": { + "description": "Maximum items per page", + "example": 1, + "format": "int64", + "type": "integer" + }, + "offset": { + "description": "Offset from first result to start of page", + "example": 1, + "format": "int64", + "type": "integer" + }, + "total": { + "description": "Total result count before paging", + "example": 1, + "format": "int64", + "type": "integer" + } + }, + "required": [ + "limit", + "offset", + "total" + ], + "type": "object" + }, "EnduroStoredPackage": { "description": "StoredPackage describes a package retrieved by the service.", "example": { @@ -551,38 +620,6 @@ ], "type": "object" }, - "ListResponseBody": { - "example": { - "items": [ - { - "aip_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", - "completed_at": "1970-01-01T00:00:01Z", - "created_at": "1970-01-01T00:00:01Z", - "id": 1, - "location_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", - "name": "abc123", - "run_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5", - "started_at": "1970-01-01T00:00:01Z", - "status": "in progress", - "workflow_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" - } - ], - "next_cursor": "abc123" - }, - "properties": { - "items": { - "$ref": "#/components/schemas/EnduroStoredPackageCollection" - }, - "next_cursor": { - "example": "abc123", - "type": "string" - } - }, - "required": [ - "items" - ], - "type": "object" - }, "Location": { "example": { "config": { @@ -1431,14 +1468,28 @@ }, { "allowEmptyValue": true, - "description": "Pagination cursor", - "example": "abc123", + "description": "Limit number of results to return", + "example": 1, "in": "query", - "name": "cursor", + "name": "limit", "schema": { - "description": "Pagination cursor", - "example": "abc123", - "type": "string" + "description": "Limit number of results to return", + "example": 1, + "format": "int64", + "type": "integer" + } + }, + { + "allowEmptyValue": true, + "description": "Offset from the beginning of the found set", + "example": 1, + "in": "query", + "name": "offset", + "schema": { + "description": "Offset from the beginning of the found set", + "example": 1, + "format": "int64", + "type": "integer" } } ], @@ -1461,10 +1512,14 @@ "workflow_id": "d1845cb6-a5ea-474a-9ab8-26f9bcd919f5" } ], - "next_cursor": "abc123" + "page": { + "limit": 1, + "offset": 1, + "total": 1 + } }, "schema": { - "$ref": "#/components/schemas/ListResponseBody" + "$ref": "#/components/schemas/EnduroPackages" } } }, diff --git a/internal/api/gen/http/openapi3.yaml b/internal/api/gen/http/openapi3.yaml index 4b512a3d5..16dab98f6 100644 --- a/internal/api/gen/http/openapi3.yaml +++ b/internal/api/gen/http/openapi3.yaml @@ -72,22 +72,33 @@ paths: - abandoned - pending example: in progress - - name: cursor + - name: limit in: query - description: Pagination cursor + description: Limit number of results to return allowEmptyValue: true schema: - type: string - description: Pagination cursor - example: abc123 - example: abc123 + type: integer + description: Limit number of results to return + example: 1 + format: int64 + example: 1 + - name: offset + in: query + description: Offset from the beginning of the found set + allowEmptyValue: true + schema: + type: integer + description: Offset from the beginning of the found set + example: 1 + format: int64 + example: 1 responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/ListResponseBody' + $ref: '#/components/schemas/EnduroPackages' example: items: - aip_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 @@ -100,7 +111,10 @@ paths: started_at: "1970-01-01T00:00:01Z" status: in progress workflow_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - next_cursor: abc123 + page: + limit: 1 + offset: 1 + total: 1 "401": description: 'unauthorized: Unauthorized response.' content: @@ -1699,6 +1713,58 @@ components: started_at: "1970-01-01T00:00:01Z" status: in progress task_id: abc123 + EnduroPackages: + type: object + properties: + items: + $ref: '#/components/schemas/EnduroStoredPackageCollection' + page: + $ref: '#/components/schemas/EnduroPage' + example: + items: + - aip_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 + completed_at: "1970-01-01T00:00:01Z" + created_at: "1970-01-01T00:00:01Z" + id: 1 + location_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 + name: abc123 + run_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 + started_at: "1970-01-01T00:00:01Z" + status: in progress + workflow_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 + page: + limit: 1 + offset: 1 + total: 1 + required: + - items + EnduroPage: + type: object + properties: + limit: + type: integer + description: Maximum items per page + example: 1 + format: int64 + offset: + type: integer + description: Offset from first result to start of page + example: 1 + format: int64 + total: + type: integer + description: Total result count before paging + example: 1 + format: int64 + description: Page represents a subset of search results. + example: + limit: 1 + offset: 1 + total: 1 + required: + - limit + - offset + - total EnduroStoredPackage: type: object properties: @@ -1831,29 +1897,6 @@ components: - temporary - timeout - fault - ListResponseBody: - type: object - properties: - items: - $ref: '#/components/schemas/EnduroStoredPackageCollection' - next_cursor: - type: string - example: abc123 - example: - items: - - aip_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - completed_at: "1970-01-01T00:00:01Z" - created_at: "1970-01-01T00:00:01Z" - id: 1 - location_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - name: abc123 - run_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - started_at: "1970-01-01T00:00:01Z" - status: in progress - workflow_id: d1845cb6-a5ea-474a-9ab8-26f9bcd919f5 - next_cursor: abc123 - required: - - items Location: type: object properties: diff --git a/internal/api/gen/http/package_/client/cli.go b/internal/api/gen/http/package_/client/cli.go index 880b3420e..55f8098d5 100644 --- a/internal/api/gen/http/package_/client/cli.go +++ b/internal/api/gen/http/package_/client/cli.go @@ -49,7 +49,7 @@ func BuildMonitorPayload(package_MonitorTicket string) (*package_.MonitorPayload // BuildListPayload builds the payload for the package list endpoint from CLI // flags. -func BuildListPayload(package_ListName string, package_ListAipID string, package_ListEarliestCreatedTime string, package_ListLatestCreatedTime string, package_ListLocationID string, package_ListStatus string, package_ListCursor string, package_ListToken string) (*package_.ListPayload, error) { +func BuildListPayload(package_ListName string, package_ListAipID string, package_ListEarliestCreatedTime string, package_ListLatestCreatedTime string, package_ListLocationID string, package_ListStatus string, package_ListLimit string, package_ListOffset string, package_ListToken string) (*package_.ListPayload, error) { var err error var name *string { @@ -109,10 +109,28 @@ func BuildListPayload(package_ListName string, package_ListAipID string, package } } } - var cursor *string + var limit *int { - if package_ListCursor != "" { - cursor = &package_ListCursor + if package_ListLimit != "" { + var v int64 + v, err = strconv.ParseInt(package_ListLimit, 10, strconv.IntSize) + val := int(v) + limit = &val + if err != nil { + return nil, fmt.Errorf("invalid value for limit, must be INT") + } + } + } + var offset *int + { + if package_ListOffset != "" { + var v int64 + v, err = strconv.ParseInt(package_ListOffset, 10, strconv.IntSize) + val := int(v) + offset = &val + if err != nil { + return nil, fmt.Errorf("invalid value for offset, must be INT") + } } } var token *string @@ -128,7 +146,8 @@ func BuildListPayload(package_ListName string, package_ListAipID string, package v.LatestCreatedTime = latestCreatedTime v.LocationID = locationID v.Status = status - v.Cursor = cursor + v.Limit = limit + v.Offset = offset v.Token = token return v, nil diff --git a/internal/api/gen/http/package_/client/encode_decode.go b/internal/api/gen/http/package_/client/encode_decode.go index a0701218c..5eee9a4ee 100644 --- a/internal/api/gen/http/package_/client/encode_decode.go +++ b/internal/api/gen/http/package_/client/encode_decode.go @@ -11,6 +11,7 @@ package client import ( "bytes" "context" + "fmt" "io" "net/http" "net/url" @@ -309,8 +310,11 @@ func EncodeListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.R if p.Status != nil { values.Add("status", *p.Status) } - if p.Cursor != nil { - values.Add("cursor", *p.Cursor) + if p.Limit != nil { + values.Add("limit", fmt.Sprintf("%v", *p.Limit)) + } + if p.Offset != nil { + values.Add("offset", fmt.Sprintf("%v", *p.Offset)) } req.URL.RawQuery = values.Encode() return nil @@ -348,11 +352,13 @@ func DecodeListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBod if err != nil { return nil, goahttp.ErrDecodingError("package", "list", err) } - err = ValidateListResponseBody(&body) - if err != nil { + p := NewListEnduroPackagesOK(&body) + view := "default" + vres := &package_views.EnduroPackages{Projected: p, View: view} + if err = package_views.ValidateEnduroPackages(vres); err != nil { return nil, goahttp.ErrValidationError("package", "list", err) } - res := NewListResultOK(&body) + res := package_.NewEnduroPackages(vres) return res, nil case http.StatusForbidden: var ( @@ -1383,19 +1389,19 @@ func BuildUploadStreamPayload(payload any, fpath string) (*package_.UploadReques }, nil } -// unmarshalEnduroStoredPackageResponseBodyToPackageEnduroStoredPackage builds -// a value of type *package_.EnduroStoredPackage from a value of type -// *EnduroStoredPackageResponseBody. -func unmarshalEnduroStoredPackageResponseBodyToPackageEnduroStoredPackage(v *EnduroStoredPackageResponseBody) *package_.EnduroStoredPackage { - res := &package_.EnduroStoredPackage{ - ID: *v.ID, +// unmarshalEnduroStoredPackageResponseBodyToPackageViewsEnduroStoredPackageView +// builds a value of type *package_views.EnduroStoredPackageView from a value +// of type *EnduroStoredPackageResponseBody. +func unmarshalEnduroStoredPackageResponseBodyToPackageViewsEnduroStoredPackageView(v *EnduroStoredPackageResponseBody) *package_views.EnduroStoredPackageView { + res := &package_views.EnduroStoredPackageView{ + ID: v.ID, Name: v.Name, LocationID: v.LocationID, - Status: *v.Status, + Status: v.Status, WorkflowID: v.WorkflowID, RunID: v.RunID, AipID: v.AipID, - CreatedAt: *v.CreatedAt, + CreatedAt: v.CreatedAt, StartedAt: v.StartedAt, CompletedAt: v.CompletedAt, } @@ -1403,6 +1409,22 @@ func unmarshalEnduroStoredPackageResponseBodyToPackageEnduroStoredPackage(v *End return res } +// unmarshalEnduroPageResponseBodyToPackageViewsEnduroPageView builds a value +// of type *package_views.EnduroPageView from a value of type +// *EnduroPageResponseBody. +func unmarshalEnduroPageResponseBodyToPackageViewsEnduroPageView(v *EnduroPageResponseBody) *package_views.EnduroPageView { + if v == nil { + return nil + } + res := &package_views.EnduroPageView{ + Limit: v.Limit, + Offset: v.Offset, + Total: v.Total, + } + + return res +} + // unmarshalEnduroPackagePreservationActionResponseBodyToPackageViewsEnduroPackagePreservationActionView // builds a value of type *package_views.EnduroPackagePreservationActionView // from a value of type *EnduroPackagePreservationActionResponseBody. diff --git a/internal/api/gen/http/package_/client/types.go b/internal/api/gen/http/package_/client/types.go index 5c7f88dce..d6322dced 100644 --- a/internal/api/gen/http/package_/client/types.go +++ b/internal/api/gen/http/package_/client/types.go @@ -54,8 +54,8 @@ type MonitorResponseBody struct { // ListResponseBody is the type of the "package" service "list" endpoint HTTP // response body. type ListResponseBody struct { - Items EnduroStoredPackageCollectionResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` - NextCursor *string `form:"next_cursor,omitempty" json:"next_cursor,omitempty" xml:"next_cursor,omitempty"` + Items EnduroStoredPackageCollectionResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + Page *EnduroPageResponseBody `form:"page,omitempty" json:"page,omitempty" xml:"page,omitempty"` } // ShowResponseBody is the type of the "package" service "show" endpoint HTTP @@ -413,6 +413,16 @@ type EnduroStoredPackageResponseBody struct { CompletedAt *string `form:"completed_at,omitempty" json:"completed_at,omitempty" xml:"completed_at,omitempty"` } +// EnduroPageResponseBody is used to define fields on response body types. +type EnduroPageResponseBody struct { + // Maximum items per page + Limit *int `form:"limit,omitempty" json:"limit,omitempty" xml:"limit,omitempty"` + // Offset from first result to start of page + Offset *int `form:"offset,omitempty" json:"offset,omitempty" xml:"offset,omitempty"` + // Total result count before paging + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + // EnduroPackagePreservationActionCollectionResponseBody is used to define // fields on response body types. type EnduroPackagePreservationActionCollectionResponseBody []*EnduroPackagePreservationActionResponseBody @@ -584,15 +594,16 @@ func NewMonitorUnauthorized(body string) package_.Unauthorized { return v } -// NewListResultOK builds a "package" service "list" endpoint result from a -// HTTP "OK" response. -func NewListResultOK(body *ListResponseBody) *package_.ListResult { - v := &package_.ListResult{ - NextCursor: body.NextCursor, - } - v.Items = make([]*package_.EnduroStoredPackage, len(body.Items)) +// NewListEnduroPackagesOK builds a "package" service "list" endpoint result +// from a HTTP "OK" response. +func NewListEnduroPackagesOK(body *ListResponseBody) *package_views.EnduroPackagesView { + v := &package_views.EnduroPackagesView{} + v.Items = make([]*package_views.EnduroStoredPackageView, len(body.Items)) for i, val := range body.Items { - v.Items[i] = unmarshalEnduroStoredPackageResponseBodyToPackageEnduroStoredPackage(val) + v.Items[i] = unmarshalEnduroStoredPackageResponseBodyToPackageViewsEnduroStoredPackageView(val) + } + if body.Page != nil { + v.Page = unmarshalEnduroPageResponseBodyToPackageViewsEnduroPageView(body.Page) } return v @@ -1007,19 +1018,6 @@ func ValidateMonitorResponseBody(body *MonitorResponseBody) (err error) { return } -// ValidateListResponseBody runs the validations defined on ListResponseBody -func ValidateListResponseBody(body *ListResponseBody) (err error) { - if body.Items == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("items", "body")) - } - if body.Items != nil { - if err2 := ValidateEnduroStoredPackageCollectionResponseBody(body.Items); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - return -} - // ValidateMoveStatusResponseBody runs the validations defined on // move_status_response_body func ValidateMoveStatusResponseBody(body *MoveStatusResponseBody) (err error) { @@ -1464,6 +1462,21 @@ func ValidateEnduroStoredPackageResponseBody(body *EnduroStoredPackageResponseBo return } +// ValidateEnduroPageResponseBody runs the validations defined on +// EnduroPageResponseBody +func ValidateEnduroPageResponseBody(body *EnduroPageResponseBody) (err error) { + if body.Limit == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("limit", "body")) + } + if body.Offset == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("offset", "body")) + } + if body.Total == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("total", "body")) + } + return +} + // ValidateEnduroPackagePreservationActionCollectionResponseBody runs the // validations defined on // EnduroPackage-Preservation-ActionCollectionResponseBody diff --git a/internal/api/gen/http/package_/server/encode_decode.go b/internal/api/gen/http/package_/server/encode_decode.go index 7d1cce477..0b6a00c26 100644 --- a/internal/api/gen/http/package_/server/encode_decode.go +++ b/internal/api/gen/http/package_/server/encode_decode.go @@ -182,9 +182,9 @@ func EncodeMonitorError(encoder func(context.Context, http.ResponseWriter) goaht // list endpoint. func EncodeListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*package_.ListResult) + res := v.(*package_views.EnduroPackages) enc := encoder(ctx, w) - body := NewListResponseBody(res) + body := NewListResponseBody(res.Projected) w.WriteHeader(http.StatusOK) return enc.Encode(body) } @@ -201,7 +201,8 @@ func DecodeListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.De latestCreatedTime *string locationID *string status *string - cursor *string + limit *int + offset *int token *string err error ) @@ -246,9 +247,27 @@ func DecodeListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.De err = goa.MergeErrors(err, goa.InvalidEnumValueError("status", *status, []any{"new", "in progress", "done", "error", "unknown", "queued", "abandoned", "pending"})) } } - cursorRaw := r.URL.Query().Get("cursor") - if cursorRaw != "" { - cursor = &cursorRaw + { + limitRaw := r.URL.Query().Get("limit") + if limitRaw != "" { + v, err2 := strconv.ParseInt(limitRaw, 10, strconv.IntSize) + if err2 != nil { + err = goa.MergeErrors(err, goa.InvalidFieldTypeError("limit", limitRaw, "integer")) + } + pv := int(v) + limit = &pv + } + } + { + offsetRaw := r.URL.Query().Get("offset") + if offsetRaw != "" { + v, err2 := strconv.ParseInt(offsetRaw, 10, strconv.IntSize) + if err2 != nil { + err = goa.MergeErrors(err, goa.InvalidFieldTypeError("offset", offsetRaw, "integer")) + } + pv := int(v) + offset = &pv + } } tokenRaw := r.Header.Get("Authorization") if tokenRaw != "" { @@ -257,7 +276,7 @@ func DecodeListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.De if err != nil { return nil, err } - payload := NewListPayload(name, aipID, earliestCreatedTime, latestCreatedTime, locationID, status, cursor, token) + payload := NewListPayload(name, aipID, earliestCreatedTime, latestCreatedTime, locationID, status, limit, offset, token) if payload.Token != nil { if strings.Contains(*payload.Token, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1119,19 +1138,19 @@ func EncodeUploadError(encoder func(context.Context, http.ResponseWriter) goahtt } } -// marshalPackageEnduroStoredPackageToEnduroStoredPackageResponseBody builds a -// value of type *EnduroStoredPackageResponseBody from a value of type -// *package_.EnduroStoredPackage. -func marshalPackageEnduroStoredPackageToEnduroStoredPackageResponseBody(v *package_.EnduroStoredPackage) *EnduroStoredPackageResponseBody { +// marshalPackageViewsEnduroStoredPackageViewToEnduroStoredPackageResponseBody +// builds a value of type *EnduroStoredPackageResponseBody from a value of type +// *package_views.EnduroStoredPackageView. +func marshalPackageViewsEnduroStoredPackageViewToEnduroStoredPackageResponseBody(v *package_views.EnduroStoredPackageView) *EnduroStoredPackageResponseBody { res := &EnduroStoredPackageResponseBody{ - ID: v.ID, + ID: *v.ID, Name: v.Name, LocationID: v.LocationID, - Status: v.Status, + Status: *v.Status, WorkflowID: v.WorkflowID, RunID: v.RunID, AipID: v.AipID, - CreatedAt: v.CreatedAt, + CreatedAt: *v.CreatedAt, StartedAt: v.StartedAt, CompletedAt: v.CompletedAt, } @@ -1139,6 +1158,22 @@ func marshalPackageEnduroStoredPackageToEnduroStoredPackageResponseBody(v *packa return res } +// marshalPackageViewsEnduroPageViewToEnduroPageResponseBody builds a value of +// type *EnduroPageResponseBody from a value of type +// *package_views.EnduroPageView. +func marshalPackageViewsEnduroPageViewToEnduroPageResponseBody(v *package_views.EnduroPageView) *EnduroPageResponseBody { + if v == nil { + return nil + } + res := &EnduroPageResponseBody{ + Limit: *v.Limit, + Offset: *v.Offset, + Total: *v.Total, + } + + return res +} + // marshalPackageViewsEnduroPackagePreservationActionViewToEnduroPackagePreservationActionResponseBody // builds a value of type *EnduroPackagePreservationActionResponseBody from a // value of type *package_views.EnduroPackagePreservationActionView. diff --git a/internal/api/gen/http/package_/server/types.go b/internal/api/gen/http/package_/server/types.go index d27575f3e..64717a8c2 100644 --- a/internal/api/gen/http/package_/server/types.go +++ b/internal/api/gen/http/package_/server/types.go @@ -54,8 +54,8 @@ type MonitorResponseBody struct { // ListResponseBody is the type of the "package" service "list" endpoint HTTP // response body. type ListResponseBody struct { - Items EnduroStoredPackageCollectionResponseBody `form:"items" json:"items" xml:"items"` - NextCursor *string `form:"next_cursor,omitempty" json:"next_cursor,omitempty" xml:"next_cursor,omitempty"` + Items EnduroStoredPackageResponseBodyCollection `form:"items" json:"items" xml:"items"` + Page *EnduroPageResponseBody `form:"page,omitempty" json:"page,omitempty" xml:"page,omitempty"` } // ShowResponseBody is the type of the "package" service "show" endpoint HTTP @@ -384,9 +384,9 @@ type UploadInternalErrorResponseBody struct { Fault bool `form:"fault" json:"fault" xml:"fault"` } -// EnduroStoredPackageCollectionResponseBody is used to define fields on +// EnduroStoredPackageResponseBodyCollection is used to define fields on // response body types. -type EnduroStoredPackageCollectionResponseBody []*EnduroStoredPackageResponseBody +type EnduroStoredPackageResponseBodyCollection []*EnduroStoredPackageResponseBody // EnduroStoredPackageResponseBody is used to define fields on response body // types. @@ -413,6 +413,16 @@ type EnduroStoredPackageResponseBody struct { CompletedAt *string `form:"completed_at,omitempty" json:"completed_at,omitempty" xml:"completed_at,omitempty"` } +// EnduroPageResponseBody is used to define fields on response body types. +type EnduroPageResponseBody struct { + // Maximum items per page + Limit int `form:"limit" json:"limit" xml:"limit"` + // Offset from first result to start of page + Offset int `form:"offset" json:"offset" xml:"offset"` + // Total result count before paging + Total int `form:"total" json:"total" xml:"total"` +} + // EnduroPackagePreservationActionResponseBodyCollection is used to define // fields on response body types. type EnduroPackagePreservationActionResponseBodyCollection []*EnduroPackagePreservationActionResponseBody @@ -498,18 +508,19 @@ func NewMonitorResponseBody(res *package_.MonitorEvent) *MonitorResponseBody { // NewListResponseBody builds the HTTP response body from the result of the // "list" endpoint of the "package" service. -func NewListResponseBody(res *package_.ListResult) *ListResponseBody { - body := &ListResponseBody{ - NextCursor: res.NextCursor, - } +func NewListResponseBody(res *package_views.EnduroPackagesView) *ListResponseBody { + body := &ListResponseBody{} if res.Items != nil { body.Items = make([]*EnduroStoredPackageResponseBody, len(res.Items)) for i, val := range res.Items { - body.Items[i] = marshalPackageEnduroStoredPackageToEnduroStoredPackageResponseBody(val) + body.Items[i] = marshalPackageViewsEnduroStoredPackageViewToEnduroStoredPackageResponseBody(val) } } else { body.Items = []*EnduroStoredPackageResponseBody{} } + if res.Page != nil { + body.Page = marshalPackageViewsEnduroPageViewToEnduroPageResponseBody(res.Page) + } return body } @@ -814,7 +825,7 @@ func NewMonitorPayload(ticket *string) *package_.MonitorPayload { } // NewListPayload builds a package service list endpoint payload. -func NewListPayload(name *string, aipID *string, earliestCreatedTime *string, latestCreatedTime *string, locationID *string, status *string, cursor *string, token *string) *package_.ListPayload { +func NewListPayload(name *string, aipID *string, earliestCreatedTime *string, latestCreatedTime *string, locationID *string, status *string, limit *int, offset *int, token *string) *package_.ListPayload { v := &package_.ListPayload{} v.Name = name v.AipID = aipID @@ -822,7 +833,8 @@ func NewListPayload(name *string, aipID *string, earliestCreatedTime *string, la v.LatestCreatedTime = latestCreatedTime v.LocationID = locationID v.Status = status - v.Cursor = cursor + v.Limit = limit + v.Offset = offset v.Token = token return v diff --git a/internal/api/gen/package_/client.go b/internal/api/gen/package_/client.go index 604cefa7e..2a7b49f64 100644 --- a/internal/api/gen/package_/client.go +++ b/internal/api/gen/package_/client.go @@ -80,13 +80,13 @@ func (c *Client) Monitor(ctx context.Context, p *MonitorPayload) (res MonitorCli // - "unauthorized" (type Unauthorized) // - "forbidden" (type Forbidden) // - error: internal error -func (c *Client) List(ctx context.Context, p *ListPayload) (res *ListResult, err error) { +func (c *Client) List(ctx context.Context, p *ListPayload) (res *EnduroPackages, err error) { var ires any ires, err = c.ListEndpoint(ctx, p) if err != nil { return } - return ires.(*ListResult), nil + return ires.(*EnduroPackages), nil } // Show calls the "show" endpoint of the "package" service. diff --git a/internal/api/gen/package_/endpoints.go b/internal/api/gen/package_/endpoints.go index 0f6670a33..e030a22cb 100644 --- a/internal/api/gen/package_/endpoints.go +++ b/internal/api/gen/package_/endpoints.go @@ -131,7 +131,12 @@ func NewListEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { if err != nil { return nil, err } - return s.List(ctx, p) + res, err := s.List(ctx, p) + if err != nil { + return nil, err + } + vres := NewViewedEnduroPackages(res, "default") + return vres, nil } } diff --git a/internal/api/gen/package_/service.go b/internal/api/gen/package_/service.go index 20868449a..02bf61d01 100644 --- a/internal/api/gen/package_/service.go +++ b/internal/api/gen/package_/service.go @@ -25,7 +25,7 @@ type Service interface { // Obtain access to the /monitor WebSocket Monitor(context.Context, *MonitorPayload, MonitorServerStream) (err error) // List all stored packages - List(context.Context, *ListPayload) (res *ListResult, err error) + List(context.Context, *ListPayload) (res *EnduroPackages, err error) // Show package by ID Show(context.Context, *ShowPayload) (res *EnduroStoredPackage, err error) // List all preservation actions by ID @@ -123,6 +123,22 @@ type EnduroPackagePreservationTask struct { type EnduroPackagePreservationTaskCollection []*EnduroPackagePreservationTask +// EnduroPackages is the result type of the package service list method. +type EnduroPackages struct { + Items EnduroStoredPackageCollection + Page *EnduroPage +} + +// Page represents a subset of search results. +type EnduroPage struct { + // Maximum items per page + Limit int + // Offset from first result to start of page + Offset int + // Total result count before paging + Total int +} + // EnduroStoredPackage is the result type of the package service show method. type EnduroStoredPackage struct { // Identifier of package @@ -159,17 +175,13 @@ type ListPayload struct { // Identifier of storage location LocationID *string Status *string - // Pagination cursor - Cursor *string + // Limit number of results to return + Limit *int + // Offset from the beginning of the found set + Offset *int Token *string } -// ListResult is the result type of the package service list method. -type ListResult struct { - Items EnduroStoredPackageCollection - NextCursor *string -} - // MonitorEvent is the result type of the package service monitor method. type MonitorEvent struct { Event interface { @@ -403,6 +415,19 @@ func MakeInternalError(err error) *goa.ServiceError { return goa.NewServiceError(err, "internal_error", false, false, false) } +// NewEnduroPackages initializes result type EnduroPackages from viewed result +// type EnduroPackages. +func NewEnduroPackages(vres *package_views.EnduroPackages) *EnduroPackages { + return newEnduroPackages(vres.Projected) +} + +// NewViewedEnduroPackages initializes viewed result type EnduroPackages from +// result type EnduroPackages using the given view. +func NewViewedEnduroPackages(res *EnduroPackages, view string) *package_views.EnduroPackages { + p := newEnduroPackagesView(res) + return &package_views.EnduroPackages{Projected: p, View: "default"} +} + // NewEnduroStoredPackage initializes result type EnduroStoredPackage from // viewed result type EnduroStoredPackage. func NewEnduroStoredPackage(vres *package_views.EnduroStoredPackage) *EnduroStoredPackage { @@ -432,6 +457,53 @@ func NewViewedEnduroPackagePreservationActions(res *EnduroPackagePreservationAct return &package_views.EnduroPackagePreservationActions{Projected: p, View: "default"} } +// newEnduroPackages converts projected type EnduroPackages to service type +// EnduroPackages. +func newEnduroPackages(vres *package_views.EnduroPackagesView) *EnduroPackages { + res := &EnduroPackages{} + if vres.Items != nil { + res.Items = newEnduroStoredPackageCollection(vres.Items) + } + if vres.Page != nil { + res.Page = newEnduroPage(vres.Page) + } + return res +} + +// newEnduroPackagesView projects result type EnduroPackages to projected type +// EnduroPackagesView using the "default" view. +func newEnduroPackagesView(res *EnduroPackages) *package_views.EnduroPackagesView { + vres := &package_views.EnduroPackagesView{} + if res.Items != nil { + vres.Items = newEnduroStoredPackageCollectionView(res.Items) + } + if res.Page != nil { + vres.Page = newEnduroPageView(res.Page) + } + return vres +} + +// newEnduroStoredPackageCollection converts projected type +// EnduroStoredPackageCollection to service type EnduroStoredPackageCollection. +func newEnduroStoredPackageCollection(vres package_views.EnduroStoredPackageCollectionView) EnduroStoredPackageCollection { + res := make(EnduroStoredPackageCollection, len(vres)) + for i, n := range vres { + res[i] = newEnduroStoredPackage(n) + } + return res +} + +// newEnduroStoredPackageCollectionView projects result type +// EnduroStoredPackageCollection to projected type +// EnduroStoredPackageCollectionView using the "default" view. +func newEnduroStoredPackageCollectionView(res EnduroStoredPackageCollection) package_views.EnduroStoredPackageCollectionView { + vres := make(package_views.EnduroStoredPackageCollectionView, len(res)) + for i, n := range res { + vres[i] = newEnduroStoredPackageView(n) + } + return vres +} + // newEnduroStoredPackage converts projected type EnduroStoredPackage to // service type EnduroStoredPackage. func newEnduroStoredPackage(vres *package_views.EnduroStoredPackageView) *EnduroStoredPackage { @@ -477,6 +549,32 @@ func newEnduroStoredPackageView(res *EnduroStoredPackage) *package_views.EnduroS return vres } +// newEnduroPage converts projected type EnduroPage to service type EnduroPage. +func newEnduroPage(vres *package_views.EnduroPageView) *EnduroPage { + res := &EnduroPage{} + if vres.Limit != nil { + res.Limit = *vres.Limit + } + if vres.Offset != nil { + res.Offset = *vres.Offset + } + if vres.Total != nil { + res.Total = *vres.Total + } + return res +} + +// newEnduroPageView projects result type EnduroPage to projected type +// EnduroPageView using the "default" view. +func newEnduroPageView(res *EnduroPage) *package_views.EnduroPageView { + vres := &package_views.EnduroPageView{ + Limit: &res.Limit, + Offset: &res.Offset, + Total: &res.Total, + } + return vres +} + // newEnduroPackagePreservationActions converts projected type // EnduroPackagePreservationActions to service type // EnduroPackagePreservationActions. diff --git a/internal/api/gen/package_/views/view.go b/internal/api/gen/package_/views/view.go index 3b44962e3..6657c02a6 100644 --- a/internal/api/gen/package_/views/view.go +++ b/internal/api/gen/package_/views/view.go @@ -13,6 +13,14 @@ import ( goa "goa.design/goa/v3/pkg" ) +// EnduroPackages is the viewed result type that is projected based on a view. +type EnduroPackages struct { + // Type to project + Projected *EnduroPackagesView + // View to render + View string +} + // EnduroStoredPackage is the viewed result type that is projected based on a // view. type EnduroStoredPackage struct { @@ -31,6 +39,16 @@ type EnduroPackagePreservationActions struct { View string } +// EnduroPackagesView is a type that runs validations on a projected type. +type EnduroPackagesView struct { + Items EnduroStoredPackageCollectionView + Page *EnduroPageView +} + +// EnduroStoredPackageCollectionView is a type that runs validations on a +// projected type. +type EnduroStoredPackageCollectionView []*EnduroStoredPackageView + // EnduroStoredPackageView is a type that runs validations on a projected type. type EnduroStoredPackageView struct { // Identifier of package @@ -55,6 +73,16 @@ type EnduroStoredPackageView struct { CompletedAt *string } +// EnduroPageView is a type that runs validations on a projected type. +type EnduroPageView struct { + // Maximum items per page + Limit *int + // Offset from first result to start of page + Offset *int + // Total result count before paging + Total *int +} + // EnduroPackagePreservationActionsView is a type that runs validations on a // projected type. type EnduroPackagePreservationActionsView struct { @@ -96,6 +124,14 @@ type EnduroPackagePreservationTaskView struct { } var ( + // EnduroPackagesMap is a map indexing the attribute names of EnduroPackages by + // view name. + EnduroPackagesMap = map[string][]string{ + "default": { + "items", + "page", + }, + } // EnduroStoredPackageMap is a map indexing the attribute names of // EnduroStoredPackage by view name. EnduroStoredPackageMap = map[string][]string{ @@ -119,6 +155,31 @@ var ( "actions", }, } + // EnduroStoredPackageCollectionMap is a map indexing the attribute names of + // EnduroStoredPackageCollection by view name. + EnduroStoredPackageCollectionMap = map[string][]string{ + "default": { + "id", + "name", + "location_id", + "status", + "workflow_id", + "run_id", + "aip_id", + "created_at", + "started_at", + "completed_at", + }, + } + // EnduroPageMap is a map indexing the attribute names of EnduroPage by view + // name. + EnduroPageMap = map[string][]string{ + "default": { + "limit", + "offset", + "total", + }, + } // EnduroPackagePreservationActionCollectionMap is a map indexing the attribute // names of EnduroPackagePreservationActionCollection by view name. EnduroPackagePreservationActionCollectionMap = map[string][]string{ @@ -195,6 +256,18 @@ var ( } ) +// ValidateEnduroPackages runs the validations defined on the viewed result +// type EnduroPackages. +func ValidateEnduroPackages(result *EnduroPackages) (err error) { + switch result.View { + case "default", "": + err = ValidateEnduroPackagesView(result.Projected) + default: + err = goa.InvalidEnumValueError("view", result.View, []any{"default"}) + } + return +} + // ValidateEnduroStoredPackage runs the validations defined on the viewed // result type EnduroStoredPackage. func ValidateEnduroStoredPackage(result *EnduroStoredPackage) (err error) { @@ -219,6 +292,34 @@ func ValidateEnduroPackagePreservationActions(result *EnduroPackagePreservationA return } +// ValidateEnduroPackagesView runs the validations defined on +// EnduroPackagesView using the "default" view. +func ValidateEnduroPackagesView(result *EnduroPackagesView) (err error) { + + if result.Items != nil { + if err2 := ValidateEnduroStoredPackageCollectionView(result.Items); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + if result.Page != nil { + if err2 := ValidateEnduroPageView(result.Page); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateEnduroStoredPackageCollectionView runs the validations defined on +// EnduroStoredPackageCollectionView using the "default" view. +func ValidateEnduroStoredPackageCollectionView(result EnduroStoredPackageCollectionView) (err error) { + for _, item := range result { + if err2 := ValidateEnduroStoredPackageView(item); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + // ValidateEnduroStoredPackageView runs the validations defined on // EnduroStoredPackageView using the "default" view. func ValidateEnduroStoredPackageView(result *EnduroStoredPackageView) (err error) { @@ -257,6 +358,21 @@ func ValidateEnduroStoredPackageView(result *EnduroStoredPackageView) (err error return } +// ValidateEnduroPageView runs the validations defined on EnduroPageView using +// the "default" view. +func ValidateEnduroPageView(result *EnduroPageView) (err error) { + if result.Limit == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("limit", "result")) + } + if result.Offset == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("offset", "result")) + } + if result.Total == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("total", "result")) + } + return +} + // ValidateEnduroPackagePreservationActionsView runs the validations defined on // EnduroPackagePreservationActionsView using the "default" view. func ValidateEnduroPackagePreservationActionsView(result *EnduroPackagePreservationActionsView) (err error) { diff --git a/internal/datatypes/package_.go b/internal/datatypes/package_.go index 8141333c9..f5bdc5847 100644 --- a/internal/datatypes/package_.go +++ b/internal/datatypes/package_.go @@ -33,7 +33,11 @@ type Package struct { } // Goa returns the API representation of the package. -func (p Package) Goa() *goapackage.EnduroStoredPackage { +func (p *Package) Goa() *goapackage.EnduroStoredPackage { + if p == nil { + return nil + } + var id uint if p.ID > 0 { id = uint(p.ID) // #nosec G115 -- range validated. diff --git a/internal/package_/convert.go b/internal/package_/convert.go index cc66dd190..b1cddc749 100644 --- a/internal/package_/convert.go +++ b/internal/package_/convert.go @@ -1,13 +1,19 @@ package package_ import ( + "errors" + "fmt" "time" + "github.com/google/uuid" "go.artefactual.dev/tools/ref" goapackage "github.com/artefactual-sdps/enduro/internal/api/gen/package_" "github.com/artefactual-sdps/enduro/internal/datatypes" "github.com/artefactual-sdps/enduro/internal/db" + "github.com/artefactual-sdps/enduro/internal/enums" + "github.com/artefactual-sdps/enduro/internal/persistence" + "github.com/artefactual-sdps/enduro/internal/timerange" ) func packageToGoaPackageCreatedEvent(p *datatypes.Package) *goapackage.PackageCreatedEvent { @@ -77,3 +83,93 @@ func preservationTaskToGoa(pt *datatypes.PreservationTask) *goapackage.EnduroPac PreservationActionID: ref.New(paID), } } + +func listPayloadToPackageFilter(payload *goapackage.ListPayload) (*persistence.PackageFilter, error) { + aipID, err := stringToUUIDPtr(payload.AipID) + if err != nil { + return nil, fmt.Errorf("aip_id: %v", err) + } + + locID, err := stringToUUIDPtr(payload.LocationID) + if err != nil { + return nil, fmt.Errorf("location_id: %v", err) + } + + var status *enums.PackageStatus + if payload.Status != nil { + s, err := enums.ParsePackageStatus(*payload.Status) + if err != nil { + return nil, fmt.Errorf("invalid status") + } + status = &s + } + + createdAt, err := parseCreatedAtRange(payload.EarliestCreatedTime, payload.LatestCreatedTime) + if err != nil { + return nil, err + } + + pf := persistence.PackageFilter{ + AIPID: aipID, + Name: payload.Name, + LocationID: locID, + Status: status, + CreatedAt: createdAt, + Sort: persistence.NewSort().AddCol("id", true), + Page: persistence.Page{ + Limit: ref.DerefZero(payload.Limit), + Offset: ref.DerefZero(payload.Offset), + }, + } + + return &pf, nil +} + +func stringToUUIDPtr(s *string) (*uuid.UUID, error) { + if s == nil { + return nil, nil + } + + u, err := uuid.Parse(*s) + if err != nil { + return nil, errors.New("invalid UUID") + } + + return &u, nil +} + +func parseCreatedAtRange(start, end *string) (*timerange.Range, error) { + if start == nil && end == nil { + return nil, nil + } + + s, err := parseTime(start) + if err != nil { + return nil, fmt.Errorf("earliest_created_time: %v", err) + } + + e, err := parseTime(end) + if err != nil { + return nil, fmt.Errorf("latest_created_time: %v", err) + } + + r, err := timerange.New(s, e) + if err != nil { + return nil, err + } + + return &r, nil +} + +func parseTime(value *string) (time.Time, error) { + if value == nil { + return time.Time{}, nil + } + + t, err := time.Parse(time.RFC3339, *value) + if err != nil { + return time.Time{}, errors.New("invalid time") + } + + return t, nil +} diff --git a/internal/package_/goa.go b/internal/package_/goa.go index cb5c33606..e2226e097 100644 --- a/internal/package_/goa.go +++ b/internal/package_/goa.go @@ -6,8 +6,6 @@ import ( "errors" "fmt" "math" - "strconv" - "strings" "time" "go.artefactual.dev/tools/ref" @@ -17,7 +15,6 @@ import ( "github.com/artefactual-sdps/enduro/internal/api/auth" goapackage "github.com/artefactual-sdps/enduro/internal/api/gen/package_" "github.com/artefactual-sdps/enduro/internal/datatypes" - "github.com/artefactual-sdps/enduro/internal/enums" ) var ErrBulkStatusUnavailable = errors.New("bulk status unavailable") @@ -30,11 +27,6 @@ type goaWrapper struct { var _ goapackage.Service = (*goaWrapper)(nil) -var patternMatchingCharReplacer = strings.NewReplacer( - "%", "\\%", - "_", "\\_", -) - var ( ErrUnauthorized error = goapackage.Unauthorized("Unauthorized") ErrForbidden error = goapackage.Forbidden("Forbidden") @@ -138,88 +130,29 @@ func (w *goaWrapper) Monitor( } // List all stored packages. It implements goapackage.Service. -func (w *goaWrapper) List(ctx context.Context, payload *goapackage.ListPayload) (*goapackage.ListResult, error) { - query := "SELECT id, name, workflow_id, run_id, aip_id, location_id, status, CONVERT_TZ(created_at, @@session.time_zone, '+00:00') AS created_at, CONVERT_TZ(started_at, @@session.time_zone, '+00:00') AS started_at, CONVERT_TZ(completed_at, @@session.time_zone, '+00:00') AS completed_at FROM package" - args := []interface{}{} - - // We extract one extra item so we can tell the next cursor. - const limit = 20 - const limitSQL = "21" - - conds := [][2]string{} - - if payload.Name != nil { - name := patternMatchingCharReplacer.Replace(*payload.Name) + "%" - args = append(args, name) - conds = append(conds, [2]string{"AND", "name LIKE ?"}) - } - if payload.AipID != nil { - args = append(args, payload.AipID) - conds = append(conds, [2]string{"AND", "aip_id = ?"}) - } - if payload.LocationID != nil { - args = append(args, payload.LocationID) - conds = append(conds, [2]string{"AND", "location_id = ?"}) - } - if payload.Status != nil { - s, err := enums.ParsePackageStatus(*payload.Status) - if err == nil { - args = append(args, s) - conds = append(conds, [2]string{"AND", "status = ?"}) - } - } - if payload.EarliestCreatedTime != nil { - args = append(args, payload.EarliestCreatedTime) - conds = append(conds, [2]string{"AND", "created_at >= ?"}) - } - if payload.LatestCreatedTime != nil { - args = append(args, payload.LatestCreatedTime) - conds = append(conds, [2]string{"AND", "created_at <= ?"}) - } - - if payload.Cursor != nil { - args = append(args, *payload.Cursor) - conds = append(conds, [2]string{"AND", "id <= ?"}) +func (w *goaWrapper) List(ctx context.Context, payload *goapackage.ListPayload) (*goapackage.EnduroPackages, error) { + if payload == nil { + payload = &goapackage.ListPayload{} } - var where string - for i, cond := range conds { - if i == 0 { - where = " WHERE " + cond[1] - continue - } - where += fmt.Sprintf(" %s %s", cond[0], cond[1]) - } - - query += where + " ORDER BY id DESC LIMIT " + limitSQL - - rows, err := w.db.QueryxContext(ctx, query, args...) + pf, err := listPayloadToPackageFilter(payload) if err != nil { - return nil, fmt.Errorf("error querying the database: %w", err) + return nil, err } - defer rows.Close() - cols := []*goapackage.EnduroStoredPackage{} - for rows.Next() { - c := datatypes.Package{} - if err := rows.StructScan(&c); err != nil { - return nil, fmt.Errorf("error scanning database result: %w", err) - } - cols = append(cols, c.Goa()) + r, pg, err := w.perSvc.ListPackages(ctx, pf) + if err != nil { + return nil, goapackage.MakeInternalError(err) } - res := &goapackage.ListResult{ - Items: cols, + items := make([]*goapackage.EnduroStoredPackage, len(r)) + for i, pkg := range r { + items[i] = pkg.Goa() } - length := len(cols) - if length > limit { - last := cols[length-1] // Capture last item. - - // We also need the last item's ID (cursor). - lastID := strconv.Itoa(int(last.ID)) // #nosec G115 -- constrained value. - res.Items = cols[:len(cols)-1] // Remove it from the results. - res.NextCursor = &lastID // Populate cursor. + res := &goapackage.EnduroPackages{ + Items: items, + Page: pg.Goa(), } return res, nil diff --git a/internal/package_/goa_test.go b/internal/package_/goa_test.go index 4bcd20eb0..6cc8a6c17 100644 --- a/internal/package_/goa_test.go +++ b/internal/package_/goa_test.go @@ -2,16 +2,28 @@ package package_ import ( "context" + "database/sql" "fmt" "testing" + "time" + "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" + "github.com/google/uuid" + "go.artefactual.dev/tools/mockutil" + "go.artefactual.dev/tools/ref" "go.uber.org/mock/gomock" "goa.design/goa/v3/security" "gotest.tools/v3/assert" "github.com/artefactual-sdps/enduro/internal/api/auth" authfake "github.com/artefactual-sdps/enduro/internal/api/auth/fake" + goapackage "github.com/artefactual-sdps/enduro/internal/api/gen/package_" + "github.com/artefactual-sdps/enduro/internal/datatypes" + "github.com/artefactual-sdps/enduro/internal/enums" + "github.com/artefactual-sdps/enduro/internal/persistence" + persistence_fake "github.com/artefactual-sdps/enduro/internal/persistence/fake" + "github.com/artefactual-sdps/enduro/internal/timerange" ) func TestJWTAuth(t *testing.T) { @@ -104,3 +116,257 @@ func TestJWTAuth(t *testing.T) { }) } } + +func nullUUID(s string) uuid.NullUUID { + return uuid.NullUUID{ + UUID: uuid.MustParse(s), + Valid: true, + } +} + +var testPackages = []*datatypes.Package{ + { + ID: 1, + Name: "Test package 1", + WorkflowID: "workflow-1", + RunID: "c5f7c35a-d5a6-4e00-b4da-b036ce5b40bc", + AIPID: nullUUID("e2ace0da-8697-453d-9ea1-4c9b62309e54"), + LocationID: nullUUID("146182ff-9923-4869-bca1-0bbc0f822025"), + Status: enums.PackageStatusDone, + CreatedAt: time.Date(2024, 9, 25, 9, 31, 10, 0, time.UTC), + StartedAt: sql.NullTime{ + Time: time.Date(2024, 9, 25, 9, 31, 11, 0, time.UTC), + Valid: true, + }, + CompletedAt: sql.NullTime{ + Time: time.Date(2024, 9, 25, 9, 31, 12, 0, time.UTC), + Valid: true, + }, + }, + { + ID: 2, + Name: "Test package 2", + WorkflowID: "workflow-2", + RunID: "d1f172c6-4ec8-4488-8a09-eef422b024cc", + AIPID: nullUUID("ffdb12f4-1735-4022-b746-a9bf4a32109b"), + LocationID: nullUUID("659a93a0-2a6a-4931-a505-f07f71f5b010"), + Status: enums.PackageStatusInProgress, + CreatedAt: time.Date(2024, 10, 1, 17, 13, 26, 0, time.UTC), + StartedAt: sql.NullTime{ + Time: time.Date(2024, 10, 1, 17, 13, 27, 0, time.UTC), + Valid: true, + }, + CompletedAt: sql.NullTime{ + Time: time.Date(2024, 10, 1, 17, 13, 28, 0, time.UTC), + Valid: true, + }, + }, +} + +func TestList(t *testing.T) { + t.Parallel() + + type test struct { + name string + payload *goapackage.ListPayload + mockRecorder func(mr *persistence_fake.MockServiceMockRecorder) + want *goapackage.EnduroPackages + wantErr string + } + for _, tt := range []test{ + { + name: "Returns all packages", + mockRecorder: func(mr *persistence_fake.MockServiceMockRecorder) { + mr.ListPackages( + mockutil.Context(), + &persistence.PackageFilter{ + Sort: persistence.NewSort().AddCol("id", true), + }, + ).Return( + testPackages, + &persistence.Page{Limit: 20, Total: 2}, + nil, + ) + }, + want: &goapackage.EnduroPackages{ + Items: goapackage.EnduroStoredPackageCollection{ + { + ID: 1, + Name: ref.New("Test package 1"), + LocationID: ref.New(uuid.MustParse("146182ff-9923-4869-bca1-0bbc0f822025")), + Status: "done", + WorkflowID: ref.New("workflow-1"), + RunID: ref.New("c5f7c35a-d5a6-4e00-b4da-b036ce5b40bc"), + AipID: ref.New("e2ace0da-8697-453d-9ea1-4c9b62309e54"), + CreatedAt: "2024-09-25T09:31:10Z", + StartedAt: ref.New("2024-09-25T09:31:11Z"), + CompletedAt: ref.New("2024-09-25T09:31:12Z"), + }, + { + ID: 2, + Name: ref.New("Test package 2"), + LocationID: ref.New(uuid.MustParse("659a93a0-2a6a-4931-a505-f07f71f5b010")), + Status: "in progress", + WorkflowID: ref.New("workflow-2"), + RunID: ref.New("d1f172c6-4ec8-4488-8a09-eef422b024cc"), + AipID: ref.New("ffdb12f4-1735-4022-b746-a9bf4a32109b"), + CreatedAt: "2024-10-01T17:13:26Z", + StartedAt: ref.New("2024-10-01T17:13:27Z"), + CompletedAt: ref.New("2024-10-01T17:13:28Z"), + }, + }, + Page: &goapackage.EnduroPage{ + Limit: 20, + Total: 2, + }, + }, + }, + { + name: "Returns filtered packages", + payload: &goapackage.ListPayload{ + Name: ref.New("Test package 1"), + AipID: ref.New("e2ace0da-8697-453d-9ea1-4c9b62309e54"), + LocationID: ref.New("146182ff-9923-4869-bca1-0bbc0f822025"), + EarliestCreatedTime: ref.New("2024-09-25T09:30:00Z"), + LatestCreatedTime: ref.New("2024-09-25T09:40:00Z"), + Status: ref.New("done"), + Limit: ref.New(10), + Offset: ref.New(1), + }, + mockRecorder: func(mr *persistence_fake.MockServiceMockRecorder) { + mr.ListPackages( + mockutil.Context(), + &persistence.PackageFilter{ + Name: ref.New("Test package 1"), + AIPID: ref.New(uuid.MustParse("e2ace0da-8697-453d-9ea1-4c9b62309e54")), + LocationID: ref.New(uuid.MustParse("146182ff-9923-4869-bca1-0bbc0f822025")), + CreatedAt: &timerange.Range{ + Start: time.Date(2024, 9, 25, 9, 30, 0, 0, time.UTC), + End: time.Date(2024, 9, 25, 9, 40, 0, 0, time.UTC), + }, + Status: ref.New(enums.PackageStatusDone), + Sort: persistence.NewSort().AddCol("id", true), + Page: persistence.Page{ + Limit: 10, + Offset: 1, + }, + }, + ).Return( + testPackages[0:1], + &persistence.Page{Limit: 10, Total: 1}, + nil, + ) + }, + want: &goapackage.EnduroPackages{ + Items: goapackage.EnduroStoredPackageCollection{ + { + ID: 1, + Name: ref.New("Test package 1"), + LocationID: ref.New(uuid.MustParse("146182ff-9923-4869-bca1-0bbc0f822025")), + Status: "done", + WorkflowID: ref.New("workflow-1"), + RunID: ref.New("c5f7c35a-d5a6-4e00-b4da-b036ce5b40bc"), + AipID: ref.New("e2ace0da-8697-453d-9ea1-4c9b62309e54"), + CreatedAt: "2024-09-25T09:31:10Z", + StartedAt: ref.New("2024-09-25T09:31:11Z"), + CompletedAt: ref.New("2024-09-25T09:31:12Z"), + }, + }, + Page: &goapackage.EnduroPage{ + Limit: 10, + Total: 1, + }, + }, + }, + { + name: "Errors on an internal service error", + payload: &goapackage.ListPayload{ + Name: ref.New("Package 42"), + }, + mockRecorder: func(mr *persistence_fake.MockServiceMockRecorder) { + mr.ListPackages( + mockutil.Context(), + &persistence.PackageFilter{ + Name: ref.New("Package 42"), + Sort: persistence.NewSort().AddCol("id", true), + }, + ).Return( + []*datatypes.Package{}, + &persistence.Page{}, + persistence.ErrNotFound, + ) + }, + wantErr: "not found error", + }, + { + name: "Errors on a bad aip_id", + payload: &goapackage.ListPayload{ + AipID: ref.New("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"), + }, + wantErr: "aip_id: invalid UUID", + }, + { + name: "Errors on a bad location_id", + payload: &goapackage.ListPayload{ + LocationID: ref.New("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"), + }, + wantErr: "location_id: invalid UUID", + }, + { + name: "Errors on a bad status", + payload: &goapackage.ListPayload{ + Status: ref.New("meditating"), + }, + wantErr: "invalid status", + }, + { + name: "Errors on a bad earliest_created_time", + payload: &goapackage.ListPayload{ + EarliestCreatedTime: ref.New("2024-15-15T25:83:52Z"), + }, + wantErr: "earliest_created_time: invalid time", + }, + { + name: "Errors on a bad latest_created_time", + payload: &goapackage.ListPayload{ + LatestCreatedTime: ref.New("2024-15-15T25:83:52Z"), + }, + wantErr: "latest_created_time: invalid time", + }, + { + name: "Errors on a bad created at range", + payload: &goapackage.ListPayload{ + EarliestCreatedTime: ref.New("2024-10-01T17:43:52Z"), + LatestCreatedTime: ref.New("2023-10-01T17:43:52Z"), + }, + wantErr: "time range: end cannot be before start", + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + ctrl := gomock.NewController(t) + svc := persistence_fake.NewMockService(ctrl) + + if tt.mockRecorder != nil { + tt.mockRecorder(svc.EXPECT()) + } + + wrapper := goaWrapper{ + packageImpl: &packageImpl{ + logger: logr.Discard(), + perSvc: svc, + }, + } + + got, err := wrapper.List(ctx, tt.payload) + if tt.wantErr != "" { + assert.Error(t, err, tt.wantErr) + return + } + assert.NilError(t, err) + assert.DeepEqual(t, got, tt.want) + }) + } +} diff --git a/internal/persistence/ent/client/package.go b/internal/persistence/ent/client/package.go index 30be5179e..fccda5bfc 100644 --- a/internal/persistence/ent/client/package.go +++ b/internal/persistence/ent/client/package.go @@ -134,12 +134,16 @@ func (c *client) UpdatePackage( } // ListPackages returns a slice of packages filtered according to f. -func (c *client) ListPackages(ctx context.Context, f persistence.PackageFilter) ( +func (c *client) ListPackages(ctx context.Context, f *persistence.PackageFilter) ( []*datatypes.Package, *persistence.Page, error, ) { res := []*datatypes.Package{} - page, whole := filterPackages(c.ent.Pkg.Query(), &f) + if f == nil { + f = &persistence.PackageFilter{} + } + + page, whole := filterPackages(c.ent.Pkg.Query(), f) r, err := page.All(ctx) if err != nil { @@ -164,7 +168,7 @@ func (c *client) ListPackages(ctx context.Context, f persistence.PackageFilter) return res, pp, err } -// filterPackages filters a package query based on filtering inputs. +// filterPackages applies the package filter f to the query q. func filterPackages(q *db.PkgQuery, f *persistence.PackageFilter) (page, whole *db.PkgQuery) { qf := NewFilter(q, SortableFields{ pkg.FieldID: {Name: "ID", Default: true}, @@ -177,9 +181,9 @@ func filterPackages(q *db.PkgQuery, f *persistence.PackageFilter) (page, whole * qf.OrderBy(f.Sort) qf.Page(f.Limit, f.Offset) - // Update the filter values with the actual values set on the query. E.g. - // calling `h.Page(0,0)` will set the query limit equal to the default page - // size. + // Update the PackageFilter values with the actual values set on the query. + // E.g. calling `h.Page(0,0)` will set the query limit equal to the default + // page size. f.Limit = qf.limit f.Offset = qf.offset diff --git a/internal/persistence/ent/client/package_test.go b/internal/persistence/ent/client/package_test.go index b7e1d24fe..d823244a2 100644 --- a/internal/persistence/ent/client/package_test.go +++ b/internal/persistence/ent/client/package_test.go @@ -379,11 +379,11 @@ func TestListPackages(t *testing.T) { page *persistence.Page } tests := []struct { - name string - data []*datatypes.Package - args persistence.PackageFilter - want results - wantErr string + name string + data []*datatypes.Package + packageFilter *persistence.PackageFilter + want results + wantErr string }{ { name: "Returns all packages", @@ -409,7 +409,6 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{}, want: results{ data: []*datatypes.Package{ { @@ -467,7 +466,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ Page: persistence.Page{Limit: 1}, }, want: results{ @@ -515,7 +514,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ Page: persistence.Page{Limit: 1, Offset: 1}, }, want: results{ @@ -564,7 +563,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ Name: ref.New("Test package 2"), }, want: results{ @@ -612,7 +611,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ AIPID: &aipID2.UUID, }, want: results{ @@ -660,7 +659,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ LocationID: &locID2.UUID, }, want: results{ @@ -708,7 +707,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ Status: ref.New(enums.PackageStatusInProgress), }, want: results{ @@ -756,7 +755,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ CreatedAt: func(t *testing.T) *timerange.Range { r, err := timerange.New( time.Now().Add(-1*time.Minute), @@ -825,7 +824,7 @@ func TestListPackages(t *testing.T) { CompletedAt: completed2, }, }, - args: persistence.PackageFilter{ + packageFilter: &persistence.PackageFilter{ CreatedAt: func(t *testing.T) *timerange.Range { r, err := timerange.New( time.Now().Add(time.Minute), @@ -861,7 +860,7 @@ func TestListPackages(t *testing.T) { } } - got, pg, err := svc.ListPackages(ctx, tt.args) + got, pg, err := svc.ListPackages(ctx, tt.packageFilter) assert.NilError(t, err) assert.DeepEqual(t, got, tt.want.data, diff --git a/internal/persistence/fake/mock_persistence.go b/internal/persistence/fake/mock_persistence.go index 0ecfd0ae6..cc027a9b3 100644 --- a/internal/persistence/fake/mock_persistence.go +++ b/internal/persistence/fake/mock_persistence.go @@ -156,7 +156,7 @@ func (c *MockServiceCreatePreservationTaskCall) DoAndReturn(f func(context.Conte } // ListPackages mocks base method. -func (m *MockService) ListPackages(arg0 context.Context, arg1 persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error) { +func (m *MockService) ListPackages(arg0 context.Context, arg1 *persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPackages", arg0, arg1) ret0, _ := ret[0].([]*datatypes.Package) @@ -184,13 +184,13 @@ func (c *MockServiceListPackagesCall) Return(arg0 []*datatypes.Package, arg1 *pe } // Do rewrite *gomock.Call.Do -func (c *MockServiceListPackagesCall) Do(f func(context.Context, persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error)) *MockServiceListPackagesCall { +func (c *MockServiceListPackagesCall) Do(f func(context.Context, *persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error)) *MockServiceListPackagesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockServiceListPackagesCall) DoAndReturn(f func(context.Context, persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error)) *MockServiceListPackagesCall { +func (c *MockServiceListPackagesCall) DoAndReturn(f func(context.Context, *persistence.PackageFilter) ([]*datatypes.Package, *persistence.Page, error)) *MockServiceListPackagesCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/internal/persistence/filter.go b/internal/persistence/filter.go index 0b5d44edc..cf91b44f4 100644 --- a/internal/persistence/filter.go +++ b/internal/persistence/filter.go @@ -3,6 +3,7 @@ package persistence import ( "github.com/google/uuid" + goapackage "github.com/artefactual-sdps/enduro/internal/api/gen/package_" "github.com/artefactual-sdps/enduro/internal/enums" "github.com/artefactual-sdps/enduro/internal/timerange" ) @@ -48,6 +49,18 @@ type Page struct { Total int } +func (p *Page) Goa() *goapackage.EnduroPage { + if p == nil { + return nil + } + + return &goapackage.EnduroPage{ + Limit: p.Limit, + Offset: p.Offset, + Total: p.Total, + } +} + type PackageFilter struct { Name *string AIPID *uuid.UUID diff --git a/internal/persistence/persistence.go b/internal/persistence/persistence.go index 50ec21422..5575bc5f1 100644 --- a/internal/persistence/persistence.go +++ b/internal/persistence/persistence.go @@ -29,7 +29,7 @@ type Service interface { // (e.g. ID, CreatedAt). CreatePackage(context.Context, *datatypes.Package) error UpdatePackage(context.Context, int, PackageUpdater) (*datatypes.Package, error) - ListPackages(context.Context, PackageFilter) ([]*datatypes.Package, *Page, error) + ListPackages(context.Context, *PackageFilter) ([]*datatypes.Package, *Page, error) CreatePreservationAction(context.Context, *datatypes.PreservationAction) error diff --git a/internal/persistence/telemetry.go b/internal/persistence/telemetry.go index 3a7e8f8d5..1b2a09d61 100644 --- a/internal/persistence/telemetry.go +++ b/internal/persistence/telemetry.go @@ -58,7 +58,7 @@ func (w *wrapper) UpdatePackage(ctx context.Context, id int, updater PackageUpda return r, nil } -func (w *wrapper) ListPackages(ctx context.Context, f PackageFilter) ([]*datatypes.Package, *Page, error) { +func (w *wrapper) ListPackages(ctx context.Context, f *PackageFilter) ([]*datatypes.Package, *Page, error) { ctx, span := w.tracer.Start(ctx, "ListPackages") defer span.End()