From 9cb4074d3984b63e3c64c4825afe740f3d5abbab Mon Sep 17 00:00:00 2001 From: Steven Choi Date: Wed, 13 Sep 2023 16:16:16 +1000 Subject: [PATCH 1/7] #777 Add API to list collections --- .../org/ala/profile/api/ApiController.groovy | 42 ++++++++++++++++++- .../org/ala/profile/api/ApiInterceptor.groovy | 2 + .../au/org/ala/profile/hub/UrlMappings.groovy | 1 + .../org/ala/profile/hub/ProfileService.groovy | 5 +++ .../ala/profile/api/ApiControllerSpec.groovy | 11 +++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy index 7b9951d2..333a5c24 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy @@ -24,7 +24,7 @@ import au.org.ala.plugins.openapi.Path type = SecuritySchemeType.HTTP, scheme = "bearer" ) -@RequireApiKey() +//@RequireApiKey() class ApiController extends BaseController { static namespace = "v1" static allowedSortFields = ['scientificNameLower', 'lastUpdated', 'dateCreated'] @@ -34,6 +34,7 @@ class ApiController extends BaseController { MapService mapService ApiService apiService + @RequireApiKey() @Path("/api/opus/{opusId}") @Operation( summary = "Get collection (opus) details", @@ -106,6 +107,41 @@ class ApiController extends BaseController { } } + @Path("/api/opus/list") + @Operation( + summary = "Get all collection (opus) details", + operationId = "/api/opus/list", + method = "GET", + responses = [ + @ApiResponse( + responseCode = "200", + content = @Content( + mediaType = "application/json", + array = @ArraySchema( + schema = @Schema( + implementation = OpusResponse.class + ) + ) + ) + ), + @ApiResponse(responseCode = "400", + description = "opusId is a required parameter"), + @ApiResponse(responseCode = "403", + description = "You do not have the necessary permissions to perform this action."), + @ApiResponse(responseCode = "405", + description = "An unexpected error has occurred while processing your request."), + @ApiResponse(responseCode = "404", + description = "Collection not found"), + @ApiResponse(responseCode = "500", + description = "An unexpected error has occurred while processing your request.") + ] + ) + def getListCollections () { + List opus = profileService.getOpusList() as List + render opus as JSON + } + + @RequireApiKey() @Path("/api/opus/{opusId}/profile") @Operation( summary = "List profiles in a collection", @@ -245,6 +281,7 @@ class ApiController extends BaseController { } } + @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}") @Operation( summary = "Get a profile in a collection", @@ -333,6 +370,7 @@ class ApiController extends BaseController { } } + @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/draft") @Operation( summary = "Get a draft profile in a collection", @@ -416,6 +454,7 @@ class ApiController extends BaseController { } } + @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/image") @Operation( summary = "Get images associated with a profile", @@ -510,6 +549,7 @@ class ApiController extends BaseController { } } + @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/attribute/{attributeId}") @Operation( summary = "Get attributes of a profile in a collection", diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy index 00220bff..f0387c28 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy @@ -26,6 +26,8 @@ class ApiInterceptor { Class controllerClass = controller?.clazz def method = controllerClass?.getMethod(actionName, [] as Class[]) + if (method.name == "getListCollections") return true + if (authorization) { if (params.opusId && (opus = profileService.getOpus(params.opusId))) { params.isOpusPrivate = opus.privateCollection diff --git a/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy b/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy index 971755e6..8d855743 100644 --- a/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy +++ b/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy @@ -263,6 +263,7 @@ class UrlMappings { get "/opus/$opusId/profile/$profileId/image" (version: "1.0", controller: "api", action: "getImages", namespace: "v1") get "/opus/$opusId/profile/$profileId/attribute/$attributeId" (version: "1.0", controller: "api", action: "getAttributes", namespace: "v1") get "/opus/$opusId/profile/$profileId/draft" (version: "1.0", controller: "api", action: "getDraftProfile", namespace: "v1") + get "/opus/list" (version: "1.0", controller: "api", action: "getListCollections", namespace: "v1") } "/openapi/$action?/$id?(.$format)?"(controller: "openApi") diff --git a/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy b/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy index d18e685b..d996e042 100644 --- a/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy +++ b/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy @@ -45,6 +45,11 @@ class ProfileService { webServiceWrapperService.get("${grailsApplication.config.profile.service.url}/opus/${encPath(opusId)}", [:], ContentType.APPLICATION_JSON, true, false, getCustomHeaderWithUserId())?.resp } + def getOpusList() { + webServiceWrapperService.get("${grailsApplication.config.profile.service.url}/opus/list", [:], ContentType.APPLICATION_JSON, false, false, null)?.resp + } + + def updateOpus(String opusId, Map json) { webService.post("${grailsApplication.config.profile.service.url}/opus/${encPath(opusId)}", json, [:], ContentType.APPLICATION_JSON, true, false, getCustomHeaderWithUserId()) } diff --git a/src/test/groovy/au/org/ala/profile/api/ApiControllerSpec.groovy b/src/test/groovy/au/org/ala/profile/api/ApiControllerSpec.groovy index ee560ec6..270a4dbd 100644 --- a/src/test/groovy/au/org/ala/profile/api/ApiControllerSpec.groovy +++ b/src/test/groovy/au/org/ala/profile/api/ApiControllerSpec.groovy @@ -149,4 +149,15 @@ class ApiControllerSpec extends Specification implements ControllerUnitTest> [[uuid: 'abc',shortName:'alatest',title:'title1',desciption:'desc1',thubnailUrl:'test.png']] + + when: + controller.getListCollections() + + then: + response.status == 200 + } } From 734f254fed08660d20f6058600bd1fb50a68724b Mon Sep 17 00:00:00 2001 From: Steven Choi Date: Wed, 13 Sep 2023 16:46:03 +1000 Subject: [PATCH 2/7] #777 remove comment --- .../controllers/au/org/ala/profile/api/ApiController.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy index 333a5c24..07969646 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy @@ -24,7 +24,7 @@ import au.org.ala.plugins.openapi.Path type = SecuritySchemeType.HTTP, scheme = "bearer" ) -//@RequireApiKey() + class ApiController extends BaseController { static namespace = "v1" static allowedSortFields = ['scientificNameLower', 'lastUpdated', 'dateCreated'] From 7109714220caef1445833f734773e15ff22d807a Mon Sep 17 00:00:00 2001 From: Steven Choi Date: Thu, 14 Sep 2023 15:50:35 +1000 Subject: [PATCH 3/7] #777 change logic and use existing Opus --- .../org/ala/profile/api/ApiController.groovy | 20 +++++++++---------- .../org/ala/profile/api/ApiInterceptor.groovy | 2 -- .../au/org/ala/profile/hub/UrlMappings.groovy | 2 +- .../org/ala/profile/hub/ProfileService.groovy | 5 ----- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy index 07969646..590c2e53 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy @@ -1,6 +1,7 @@ package au.org.ala.profile.api import au.ala.org.ws.security.RequireApiKey +import au.org.ala.profile.domain.CollectionList import au.org.ala.profile.hub.BaseController import au.org.ala.profile.hub.MapService import au.org.ala.profile.hub.ProfileService @@ -25,6 +26,7 @@ import au.org.ala.plugins.openapi.Path scheme = "bearer" ) +@RequireApiKey() class ApiController extends BaseController { static namespace = "v1" static allowedSortFields = ['scientificNameLower', 'lastUpdated', 'dateCreated'] @@ -34,7 +36,6 @@ class ApiController extends BaseController { MapService mapService ApiService apiService - @RequireApiKey() @Path("/api/opus/{opusId}") @Operation( summary = "Get collection (opus) details", @@ -107,10 +108,10 @@ class ApiController extends BaseController { } } - @Path("/api/opus/list") + @Path("/api/opus") @Operation( - summary = "Get all collection (opus) details", - operationId = "/api/opus/list", + summary = "Get all public collections", + operationId = "/api/opus", method = "GET", responses = [ @ApiResponse( @@ -137,11 +138,12 @@ class ApiController extends BaseController { ] ) def getListCollections () { - List opus = profileService.getOpusList() as List - render opus as JSON + List opus = profileService.getOpus() as List + List filtered = opus.findAll(it-> !it.privateCollection) + .collect{new CollectionList(uuid: it.uuid, shortName:it.shortName, title:it.title, thumbnailUrl:it.thumbnailUrl, description:it.description)} + render filtered as JSON } - @RequireApiKey() @Path("/api/opus/{opusId}/profile") @Operation( summary = "List profiles in a collection", @@ -281,7 +283,6 @@ class ApiController extends BaseController { } } - @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}") @Operation( summary = "Get a profile in a collection", @@ -370,7 +371,6 @@ class ApiController extends BaseController { } } - @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/draft") @Operation( summary = "Get a draft profile in a collection", @@ -454,7 +454,6 @@ class ApiController extends BaseController { } } - @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/image") @Operation( summary = "Get images associated with a profile", @@ -549,7 +548,6 @@ class ApiController extends BaseController { } } - @RequireApiKey() @Path("/api/opus/{opusId}/profile/{profileId}/attribute/{attributeId}") @Operation( summary = "Get attributes of a profile in a collection", diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy index f0387c28..00220bff 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy @@ -26,8 +26,6 @@ class ApiInterceptor { Class controllerClass = controller?.clazz def method = controllerClass?.getMethod(actionName, [] as Class[]) - if (method.name == "getListCollections") return true - if (authorization) { if (params.opusId && (opus = profileService.getOpus(params.opusId))) { params.isOpusPrivate = opus.privateCollection diff --git a/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy b/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy index 8d855743..8e6e0d02 100644 --- a/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy +++ b/grails-app/controllers/au/org/ala/profile/hub/UrlMappings.groovy @@ -263,7 +263,7 @@ class UrlMappings { get "/opus/$opusId/profile/$profileId/image" (version: "1.0", controller: "api", action: "getImages", namespace: "v1") get "/opus/$opusId/profile/$profileId/attribute/$attributeId" (version: "1.0", controller: "api", action: "getAttributes", namespace: "v1") get "/opus/$opusId/profile/$profileId/draft" (version: "1.0", controller: "api", action: "getDraftProfile", namespace: "v1") - get "/opus/list" (version: "1.0", controller: "api", action: "getListCollections", namespace: "v1") + get "/opus" (version: "1.0", controller: "api", action: "getListCollections", namespace: "v1") } "/openapi/$action?/$id?(.$format)?"(controller: "openApi") diff --git a/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy b/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy index d996e042..d18e685b 100644 --- a/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy +++ b/grails-app/services/au/org/ala/profile/hub/ProfileService.groovy @@ -45,11 +45,6 @@ class ProfileService { webServiceWrapperService.get("${grailsApplication.config.profile.service.url}/opus/${encPath(opusId)}", [:], ContentType.APPLICATION_JSON, true, false, getCustomHeaderWithUserId())?.resp } - def getOpusList() { - webServiceWrapperService.get("${grailsApplication.config.profile.service.url}/opus/list", [:], ContentType.APPLICATION_JSON, false, false, null)?.resp - } - - def updateOpus(String opusId, Map json) { webService.post("${grailsApplication.config.profile.service.url}/opus/${encPath(opusId)}", json, [:], ContentType.APPLICATION_JSON, true, false, getCustomHeaderWithUserId()) } From 9f28e226a531f2aa901c070e28ad4ca1654c63ae Mon Sep 17 00:00:00 2001 From: Steven Choi Date: Thu, 14 Sep 2023 15:51:43 +1000 Subject: [PATCH 4/7] #777 add pojo --- .../au/org/ala/profile/domain/CollectionList.groovy | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy diff --git a/grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy b/grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy new file mode 100644 index 00000000..56a1ee62 --- /dev/null +++ b/grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy @@ -0,0 +1,12 @@ +package au.org.ala.profile.domain + +import groovy.transform.ToString + +@ToString +class CollectionList { + String uuid + String shortName + String title + String thumbnailUrl + String description +} From d6e3c9dbb6575705fb466f40c5263781cac08726 Mon Sep 17 00:00:00 2001 From: Steven Choi Date: Thu, 14 Sep 2023 16:59:46 +1000 Subject: [PATCH 5/7] #777 add @GrantAccess and logic for grant access --- .../au/org/ala/profile/api/ApiController.groovy | 2 ++ .../au/org/ala/profile/api/ApiInterceptor.groovy | 3 +++ .../au/org/ala/profile/security/GrantAccess.groovy | 13 +++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 src/main/groovy/au/org/ala/profile/security/GrantAccess.groovy diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy index 590c2e53..4820f585 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy @@ -5,6 +5,7 @@ import au.org.ala.profile.domain.CollectionList import au.org.ala.profile.hub.BaseController import au.org.ala.profile.hub.MapService import au.org.ala.profile.hub.ProfileService +import au.org.ala.profile.security.GrantAccess import au.org.ala.profile.security.RequiresAccessToken import grails.converters.JSON @@ -108,6 +109,7 @@ class ApiController extends BaseController { } } + @GrantAccess @Path("/api/opus") @Operation( summary = "Get all public collections", diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy index 00220bff..7ed11ca5 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiInterceptor.groovy @@ -2,6 +2,7 @@ package au.org.ala.profile.api import au.org.ala.profile.hub.ProfileService import au.org.ala.profile.security.RequiresAccessToken +import au.org.ala.profile.security.GrantAccess import au.org.ala.web.AuthService import grails.converters.JSON import org.apache.http.HttpStatus @@ -38,6 +39,8 @@ class ApiInterceptor { } else { authorised = true } + } else if (method?.isAnnotationPresent(GrantAccess)){ + authorised = true } } diff --git a/src/main/groovy/au/org/ala/profile/security/GrantAccess.groovy b/src/main/groovy/au/org/ala/profile/security/GrantAccess.groovy new file mode 100644 index 00000000..b32eb50a --- /dev/null +++ b/src/main/groovy/au/org/ala/profile/security/GrantAccess.groovy @@ -0,0 +1,13 @@ +package au.org.ala.profile.security + +import java.lang.annotation.* + +/** + * Annotation to check that a valid collection-specific access token has been provided. + */ +@Target([ElementType.TYPE, ElementType.METHOD]) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GrantAccess { + +} \ No newline at end of file From ce5f56d46212ae7a9c527a1fd53582a8efdb79f8 Mon Sep 17 00:00:00 2001 From: steven choi Date: Mon, 9 Oct 2023 13:55:33 +1100 Subject: [PATCH 6/7] #777 fix them as comments --- .../controllers/au/org/ala/profile/api/ApiController.groovy | 3 +-- .../au/org/ala/profile/{domain => api}/CollectionList.groovy | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename grails-app/controllers/au/org/ala/profile/{domain => api}/CollectionList.groovy (83%) diff --git a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy index 4820f585..06580107 100644 --- a/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/ApiController.groovy @@ -1,7 +1,6 @@ package au.org.ala.profile.api import au.ala.org.ws.security.RequireApiKey -import au.org.ala.profile.domain.CollectionList import au.org.ala.profile.hub.BaseController import au.org.ala.profile.hub.MapService import au.org.ala.profile.hub.ProfileService @@ -122,7 +121,7 @@ class ApiController extends BaseController { mediaType = "application/json", array = @ArraySchema( schema = @Schema( - implementation = OpusResponse.class + implementation = CollectionList.class ) ) ) diff --git a/grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy b/grails-app/controllers/au/org/ala/profile/api/CollectionList.groovy similarity index 83% rename from grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy rename to grails-app/controllers/au/org/ala/profile/api/CollectionList.groovy index 56a1ee62..e5f0131b 100644 --- a/grails-app/controllers/au/org/ala/profile/domain/CollectionList.groovy +++ b/grails-app/controllers/au/org/ala/profile/api/CollectionList.groovy @@ -1,4 +1,4 @@ -package au.org.ala.profile.domain +package au.org.ala.profile.api import groovy.transform.ToString From b446fc1147aca712e9c6cd5d956ae6c790258cab Mon Sep 17 00:00:00 2001 From: steven choi Date: Mon, 9 Oct 2023 14:41:32 +1100 Subject: [PATCH 7/7] #777 move class --- .../main/groovy}/au/org/ala/profile/api/CollectionList.groovy | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {grails-app/controllers => src/main/groovy}/au/org/ala/profile/api/CollectionList.groovy (100%) diff --git a/grails-app/controllers/au/org/ala/profile/api/CollectionList.groovy b/src/main/groovy/au/org/ala/profile/api/CollectionList.groovy similarity index 100% rename from grails-app/controllers/au/org/ala/profile/api/CollectionList.groovy rename to src/main/groovy/au/org/ala/profile/api/CollectionList.groovy