diff --git a/backend/web/src/main/java/org/opencdmp/controllers/PrincipalController.java b/backend/web/src/main/java/org/opencdmp/controllers/PrincipalController.java index 641f5bcfd..295c60999 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/PrincipalController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/PrincipalController.java @@ -14,7 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.opencdmp.audit.AuditableAction; import org.opencdmp.controllers.swagger.SwaggerHelpers; import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader; -import org.opencdmp.model.Lock; +import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; import org.opencdmp.model.Tenant; import org.opencdmp.models.Account; import org.opencdmp.models.AccountBuilder; @@ -32,84 +32,89 @@ import java.util.List; @RestController @RequestMapping("/api/principal/") @Tag(name = "Principal", description = "Get user account information") +@SwaggerCommonErrorResponses public class PrincipalController { - private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class)); - private final AuditService auditService; - private final CurrentPrincipalResolver currentPrincipalResolver; - private final AccountBuilder accountBuilder; - private final TenantService tenantService; + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class)); - @Autowired - public PrincipalController( - CurrentPrincipalResolver currentPrincipalResolver, - AccountBuilder accountBuilder, - AuditService auditService, TenantService tenantService) { - this.currentPrincipalResolver = currentPrincipalResolver; - this.accountBuilder = accountBuilder; - this.auditService = auditService; - this.tenantService = tenantService; - } + private final AuditService auditService; - @RequestMapping(path = "me", method = RequestMethod.GET ) - @OperationWithTenantHeader(summary = "Fetch auth information of the logged in user", description = "", - responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( - schema = @Schema( - implementation = Account.class - )) - )) - public Account me( - @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet - ) throws InvalidApplicationException { - logger.debug("me"); + private final CurrentPrincipalResolver currentPrincipalResolver; - if (fieldSet == null || fieldSet.isEmpty()) { - fieldSet = new BaseFieldSet( - Account._isAuthenticated, - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._subject), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._userId), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._name), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._scope), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._client), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._issuedAt), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._notBefore), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt), - BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more), - BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._avatarUrl), - BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._language), - BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._culture), - BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._timezone), - Account._permissions, - BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._id), - BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._name), - BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._code)); - } + private final AccountBuilder accountBuilder; - MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); + private final TenantService tenantService; - Account me = this.accountBuilder.build(fieldSet, principal); + @Autowired + public PrincipalController( + CurrentPrincipalResolver currentPrincipalResolver, + AccountBuilder accountBuilder, + AuditService auditService, TenantService tenantService) { + this.currentPrincipalResolver = currentPrincipalResolver; + this.accountBuilder = accountBuilder; + this.auditService = auditService; + this.tenantService = tenantService; + } - this.auditService.track(AuditableAction.Principal_Lookup); - //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + @RequestMapping(path = "me", method = RequestMethod.GET) + @OperationWithTenantHeader(summary = "Fetch auth information of the logged in user", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Account.class + )) + )) + public Account me( + @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet + ) throws InvalidApplicationException { + logger.debug("me"); - return me; - } + if (fieldSet == null || fieldSet.isEmpty()) { + fieldSet = new BaseFieldSet( + Account._isAuthenticated, + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._subject), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._userId), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._name), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._scope), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._client), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._issuedAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._notBefore), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt), + BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._avatarUrl), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._language), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._culture), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._timezone), + Account._permissions, + BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._id), + BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._name), + BaseFieldSet.asIndexer(Account._selectedTenant, Tenant._code)); + } - @GetMapping("my-tenants") - @OperationWithTenantHeader(summary = "Fetch a list with the tenants the user belongs to", description = "", - responses = @ApiResponse(description = "OK", responseCode = "200")) - public List myTenants( - @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet - ) { - logger.debug("my-tenants"); + MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); - List models = this.tenantService.myTenants(fieldSet); + Account me = this.accountBuilder.build(fieldSet, principal); - this.auditService.track(AuditableAction.Principal_MyTenants); - //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); - - return models; - } + this.auditService.track(AuditableAction.Principal_Lookup); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return me; + } + + @GetMapping("my-tenants") + @OperationWithTenantHeader(summary = "Fetch a list with the tenants the user belongs to", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) + public List myTenants( + @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet + ) { + logger.debug("my-tenants"); + + List models = this.tenantService.myTenants(fieldSet); + + this.auditService.track(AuditableAction.Principal_MyTenants); + //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); + + return models; + } } diff --git a/backend/web/src/main/java/org/opencdmp/controllers/ReferenceController.java b/backend/web/src/main/java/org/opencdmp/controllers/ReferenceController.java index 94707022a..0085dc0bb 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/ReferenceController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/ReferenceController.java @@ -12,7 +12,6 @@ import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.validation.ValidationFilterAnnotation; -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; @@ -33,7 +32,6 @@ import org.opencdmp.data.ReferenceEntity; import org.opencdmp.model.builder.reference.ReferenceBuilder; import org.opencdmp.model.censorship.reference.ReferenceCensor; import org.opencdmp.model.persist.ReferencePersist; -import org.opencdmp.model.planblueprint.PlanBlueprint; import org.opencdmp.model.reference.Reference; import org.opencdmp.model.result.QueryResult; import org.opencdmp.query.ReferenceQuery; @@ -74,7 +72,6 @@ public class ReferenceController { private final MessageSource messageSource; - @Autowired public ReferenceController( BuilderFactory builderFactory, @@ -126,10 +123,27 @@ public class ReferenceController { return new QueryResult<>(models, count); } - @PostMapping("search") - @Hidden - //TODO thgiannos add swagger docs + @OperationWithTenantHeader(summary = "Query all references including results from external APIs", description = SwaggerHelpers.Reference.endpoint_search, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.Reference.endpoint_search_request_body, content = @Content( + examples = { + @ExampleObject( + name = "Pagination and projection", + description = "Simple paginated request using a property projection list and pagination info", + value = SwaggerHelpers.Reference.endpoint_search_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = Reference.class + ) + ), + examples = @ExampleObject( + name = "First page", + description = "Example with the first page of paginated results", + value = SwaggerHelpers.Reference.endpoint_search_response_example + )))) + @Swagger404 public List searchReferenceWithDefinition(@RequestBody ReferenceSearchLookup lookup) throws MyNotFoundException, InvalidApplicationException { logger.debug("search with db definition {}", Reference.class.getSimpleName()); @@ -138,7 +152,7 @@ public class ReferenceController { List references = this.referenceService.searchReferenceData(lookup); this.auditService.track(AuditableAction.Reference_Search, "lookup", lookup); - + return references; } diff --git a/backend/web/src/main/java/org/opencdmp/controllers/UserController.java b/backend/web/src/main/java/org/opencdmp/controllers/UserController.java index 0683fd296..908e8df14 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/UserController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/UserController.java @@ -12,7 +12,6 @@ import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.validation.ValidationFilterAnnotation; -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; @@ -37,9 +36,11 @@ import org.opencdmp.model.builder.PlanAssociatedUserBuilder; import org.opencdmp.model.builder.UserBuilder; import org.opencdmp.model.censorship.PlanAssociatedUserCensor; import org.opencdmp.model.censorship.UserCensor; -import org.opencdmp.model.persist.*; +import org.opencdmp.model.persist.UserMergeRequestPersist; +import org.opencdmp.model.persist.UserPersist; +import org.opencdmp.model.persist.UserRolePatchPersist; +import org.opencdmp.model.persist.UserTenantUsersInviteRequest; import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist; -import org.opencdmp.model.plan.Plan; import org.opencdmp.model.result.QueryResult; import org.opencdmp.model.user.User; import org.opencdmp.query.UserQuery; @@ -143,7 +144,25 @@ public class UserController { } @PostMapping("plan-associated/query") - @Hidden + @OperationWithTenantHeader(summary = "Query all plan associated users", description = SwaggerHelpers.User.endpoint_query_plan_associated, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.User.endpoint_query_request_body, content = @Content( + examples = { + @ExampleObject( + name = "Pagination and projection", + description = "Simple paginated request using a property projection list and pagination info", + value = SwaggerHelpers.User.endpoint_query_plan_associated_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = PlanAssociatedUser.class + ) + ), + examples = @ExampleObject( + name = "First page", + description = "Example with the first page of paginated results", + value = SwaggerHelpers.User.endpoint_query_plan_associated_response_example + )))) public QueryResult queryPlanAssociated(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", User.class.getSimpleName()); @@ -219,8 +238,11 @@ public class UserController { } @GetMapping("/export/csv/{hasTenantAdminMode}") - @Hidden - public ResponseEntity exportCsv(@PathVariable("hasTenantAdminMode") Boolean hasTenantAdminMode) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { + @OperationWithTenantHeader(summary = "Export users in a .csv file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) + public ResponseEntity exportCsv( + @Parameter(name = "hasTenantAdminMode", description = "Controls whether to fetch users as a tenant admin or not", example = "false", required = true) @PathVariable("hasTenantAdminMode") Boolean hasTenantAdminMode + ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { logger.debug(new MapLogEntry("export" + User.class.getSimpleName()).And("hasTenantAdminMode", hasTenantAdminMode)); // this.censorFactory.censor(UserCensor.class).censor(fieldSet, null); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java index e3ba6bc6b..34cc0a938 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java @@ -3346,6 +3346,190 @@ public final class SwaggerHelpers { } """; + public static final String endpoint_search = + """ + This endpoint is used to fetch all the available references.
+ It also allows to restrict the results using a query object passed in the request body.
+ """; + + public static final String endpoint_search_request_body = + """ + Let's explore the options this object gives us. + + ### General query parameters: + +
    +
  • page: + This is an object controlling the pagination of the results. It contains two properties. +
  • +
      +
    • offset: + How many records to omit. +
    • +
    • size: + How many records to include in each page. +
    • +
    +
+ + For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object: + + ```JSON + { + "offset": 30, + "size": 15 + } + ``` + +
    +
  • order: + This is an object controlling the ordering of the results. + It contains a list of strings called items with the names of the properties to use. +
    If the name of the property is prefixed with a '-', the ordering direction is DESC. Otherwise, it is ASC. +
  • +
+ + For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object: + + ```JSON + { + "items": [ + "-createdAt" + ], + } + ``` + +
    +
  • metadata: + This is an object containing metadata for the request. There is only one available option. +
      +
    • countAll: + If this is set to true, the count property included in the response will account for all the records regardless the pagination, + with all the rest of filtering options applied of course. + Otherwise, if it is set to false or not present, only the returned results will be counted. +
      The first option is useful for the UI clients to calculate how many result pages are available. +
    • +
    +
  • +
  • project: + This is an object controlling the data projection of the results. + It contains a list of strings called fields with the names of the properties to project. +
    You can also include properties that are deeper in the object tree by prefixing them with dots. +
  • +
+ + ### Reference specific query parameters: + +
    +
  • like: + If there is a like parameter present in the query, only the reference entities that include the contents of the parameter either in their labels, descriptions or the references will be in the response. +
  • +
  • typeId: + This is the type id of the references we want in the response.
    If empty, every record is included. +
  • +
  • key: + This is the id of the external source (API) we want results from. +
    If not present, no external reference is included. +
  • +
  • dependencyReferences: + This is a list and determines which records we want to include in the response, based on the references they depend on. +
    If not present, every record is included. +
  • +
+ """; + + public static final String endpoint_search_request_body_example = + """ + { + "project":{ + "fields":[ + "id", + "hash", + "label", + "type", + "type.id", + "description", + "definition.fields.code", + "definition.fields.dataType", + "definition.fields.value", + "reference", + "abbreviation", + "source", + "sourceType" + ] + }, + "page":{ + "size":100, + "offset":0 + }, + "typeId":"5b9c284f-f041-4995-96cc-fad7ad13289c", + "dependencyReferences":[ + \s + ], + "order":{ + "items":[ + "label" + ] + } + } + """; + + public static final String endpoint_search_response_example = + """ + [ + { + "label": "A Randomized, Double-Blind, Placebo-Controlled, Multi-Center Study to Evaluate the Efficacy of ManNAc in Subjects with GNE Myopathy (nih_________::5U01AR070498-04)", + "type": { + "id": "5b9c284f-f041-4995-96cc-fad7ad13289c" + }, + "description": "A Randomized, Double-Blind, Placebo-Controlled, Multi-Center Study to Evaluate the Efficacy of ManNAc in Subjects with GNE Myopathy", + "definition": { + "fields": [ + { + "code": "referenceType", + "dataType": 0, + "value": "Grants" + }, + { + "code": "key", + "dataType": 0, + "value": "openaire" + } + ] + }, + "reference": "nih_________::5U01AR070498-04", + "source": "openaire", + "sourceType": 1, + "hash": "" + }, + { + "label": "A genome scale census of virulence factors in the major mould pathogen of human lungs, Aspergillus fumigatus (ukri________::1640253)", + "type": { + "id": "5b9c284f-f041-4995-96cc-fad7ad13289c" + }, + "description": "A genome scale census of virulence factors in the major mould pathogen of human lungs, Aspergillus fumigatus", + "definition": { + "fields": [ + { + "code": "referenceType", + "dataType": 0, + "value": "Grants" + }, + { + "code": "key", + "dataType": 0, + "value": "openaire" + } + ] + }, + "reference": "ukri________::1640253", + "source": "openaire", + "sourceType": 1, + "hash": "" + } + ] + """; + } public static final class ReferenceType { @@ -3779,6 +3963,12 @@ public final class SwaggerHelpers { It also allows to restrict the results using a query object passed in the request body.
"""; + public static final String endpoint_query_plan_associated = + """ + This endpoint is used to fetch all the available users.
+ It also allows to restrict the results using a query object passed in the request body.
+ """; + public static final String endpoint_query_request_body = """ Let's explore the options this object gives us. @@ -3908,6 +4098,32 @@ public final class SwaggerHelpers { } """; + public static final String endpoint_query_plan_associated_request_body_example = + """ + { + "project":{ + "fields":[ + "id", + "name", + "email" + ] + }, + "page":{ + "size":100, + "offset":0 + }, + "isActive":[ + 1 + ], + "order":{ + "items":[ + "name" + ] + }, + "like":"user%" + } + """; + public static final String endpoint_query_response_example = """ { @@ -4241,6 +4457,25 @@ public final class SwaggerHelpers { } """; + public static final String endpoint_query_plan_associated_response_example = + """ + { + "items":[ + { + "id":"d26916c6-8763-450e-9048-b06e1114d0b4", + "name":"user3 user3", + "email":"user3@dmp.gr" + }, + { + "id":"02832fb6-0b12-469f-a886-7685406959d4", + "name":"user4", + "email":"user4@dmp.gr" + } + ], + "count":2 + } + """; + } public static final class Principal {