Swagger fixes and additions

This commit is contained in:
Thomas Georgios Giannos 2024-07-08 12:23:15 +03:00
parent bba4834e21
commit 2712a3d20e
4 changed files with 357 additions and 81 deletions

View File

@ -14,7 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import org.opencdmp.audit.AuditableAction; import org.opencdmp.audit.AuditableAction;
import org.opencdmp.controllers.swagger.SwaggerHelpers; import org.opencdmp.controllers.swagger.SwaggerHelpers;
import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader; 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.model.Tenant;
import org.opencdmp.models.Account; import org.opencdmp.models.Account;
import org.opencdmp.models.AccountBuilder; import org.opencdmp.models.AccountBuilder;
@ -32,84 +32,89 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/api/principal/") @RequestMapping("/api/principal/")
@Tag(name = "Principal", description = "Get user account information") @Tag(name = "Principal", description = "Get user account information")
@SwaggerCommonErrorResponses
public class PrincipalController { public class PrincipalController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
private final AuditService auditService;
private final CurrentPrincipalResolver currentPrincipalResolver; private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
private final AccountBuilder accountBuilder;
private final TenantService tenantService;
@Autowired private final AuditService auditService;
public PrincipalController(
CurrentPrincipalResolver currentPrincipalResolver,
AccountBuilder accountBuilder,
AuditService auditService, TenantService tenantService) {
this.currentPrincipalResolver = currentPrincipalResolver;
this.accountBuilder = accountBuilder;
this.auditService = auditService;
this.tenantService = tenantService;
}
@RequestMapping(path = "me", method = RequestMethod.GET ) private final CurrentPrincipalResolver currentPrincipalResolver;
@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");
if (fieldSet == null || fieldSet.isEmpty()) { private final AccountBuilder accountBuilder;
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));
}
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); @RequestMapping(path = "me", method = RequestMethod.GET)
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action); @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") MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
@OperationWithTenantHeader(summary = "Fetch a list with the tenants the user belongs to", description = "",
responses = @ApiResponse(description = "OK", responseCode = "200"))
public List<Tenant> myTenants(
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) {
logger.debug("my-tenants");
List<Tenant> models = this.tenantService.myTenants(fieldSet); Account me = this.accountBuilder.build(fieldSet, principal);
this.auditService.track(AuditableAction.Principal_MyTenants); this.auditService.track(AuditableAction.Principal_Lookup);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action); //auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return models; 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<Tenant> myTenants(
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) {
logger.debug("my-tenants");
List<Tenant> models = this.tenantService.myTenants(fieldSet);
this.auditService.track(AuditableAction.Principal_MyTenants);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return models;
}
} }

View File

@ -12,7 +12,6 @@ import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation; 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.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content; 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.builder.reference.ReferenceBuilder;
import org.opencdmp.model.censorship.reference.ReferenceCensor; import org.opencdmp.model.censorship.reference.ReferenceCensor;
import org.opencdmp.model.persist.ReferencePersist; import org.opencdmp.model.persist.ReferencePersist;
import org.opencdmp.model.planblueprint.PlanBlueprint;
import org.opencdmp.model.reference.Reference; import org.opencdmp.model.reference.Reference;
import org.opencdmp.model.result.QueryResult; import org.opencdmp.model.result.QueryResult;
import org.opencdmp.query.ReferenceQuery; import org.opencdmp.query.ReferenceQuery;
@ -74,7 +72,6 @@ public class ReferenceController {
private final MessageSource messageSource; private final MessageSource messageSource;
@Autowired @Autowired
public ReferenceController( public ReferenceController(
BuilderFactory builderFactory, BuilderFactory builderFactory,
@ -126,10 +123,27 @@ public class ReferenceController {
return new QueryResult<>(models, count); return new QueryResult<>(models, count);
} }
@PostMapping("search") @PostMapping("search")
@Hidden @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(
//TODO thgiannos add swagger docs 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<Reference> searchReferenceWithDefinition(@RequestBody ReferenceSearchLookup lookup) throws MyNotFoundException, InvalidApplicationException { public List<Reference> searchReferenceWithDefinition(@RequestBody ReferenceSearchLookup lookup) throws MyNotFoundException, InvalidApplicationException {
logger.debug("search with db definition {}", Reference.class.getSimpleName()); logger.debug("search with db definition {}", Reference.class.getSimpleName());
@ -138,7 +152,7 @@ public class ReferenceController {
List<Reference> references = this.referenceService.searchReferenceData(lookup); List<Reference> references = this.referenceService.searchReferenceData(lookup);
this.auditService.track(AuditableAction.Reference_Search, "lookup", lookup); this.auditService.track(AuditableAction.Reference_Search, "lookup", lookup);
return references; return references;
} }

View File

@ -12,7 +12,6 @@ import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation; 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.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content; 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.builder.UserBuilder;
import org.opencdmp.model.censorship.PlanAssociatedUserCensor; import org.opencdmp.model.censorship.PlanAssociatedUserCensor;
import org.opencdmp.model.censorship.UserCensor; 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.persist.actionconfirmation.RemoveCredentialRequestPersist;
import org.opencdmp.model.plan.Plan;
import org.opencdmp.model.result.QueryResult; import org.opencdmp.model.result.QueryResult;
import org.opencdmp.model.user.User; import org.opencdmp.model.user.User;
import org.opencdmp.query.UserQuery; import org.opencdmp.query.UserQuery;
@ -143,7 +144,25 @@ public class UserController {
} }
@PostMapping("plan-associated/query") @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<PlanAssociatedUser> queryPlanAssociated(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException { public QueryResult<PlanAssociatedUser> queryPlanAssociated(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", User.class.getSimpleName()); logger.debug("querying {}", User.class.getSimpleName());
@ -219,8 +238,11 @@ public class UserController {
} }
@GetMapping("/export/csv/{hasTenantAdminMode}") @GetMapping("/export/csv/{hasTenantAdminMode}")
@Hidden @OperationWithTenantHeader(summary = "Export users in a .csv file", description = "",
public ResponseEntity<byte[]> exportCsv(@PathVariable("hasTenantAdminMode") Boolean hasTenantAdminMode) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { responses = @ApiResponse(description = "OK", responseCode = "200"))
public ResponseEntity<byte[]> 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)); logger.debug(new MapLogEntry("export" + User.class.getSimpleName()).And("hasTenantAdminMode", hasTenantAdminMode));
// this.censorFactory.censor(UserCensor.class).censor(fieldSet, null); // this.censorFactory.censor(UserCensor.class).censor(fieldSet, null);

View File

@ -3346,6 +3346,190 @@ public final class SwaggerHelpers {
} }
"""; """;
public static final String endpoint_search =
"""
This endpoint is used to fetch all the available references.<br/>
It also allows to restrict the results using a query object passed in the request body.<br/>
""";
public static final String endpoint_search_request_body =
"""
Let's explore the options this object gives us.
### <u>General query parameters:</u>
<ul>
<li><b>page:</b>
This is an object controlling the pagination of the results. It contains two properties.
</li>
<ul>
<li><b>offset:</b>
How many records to omit.
</li>
<li><b>size:</b>
How many records to include in each page.
</li>
</ul>
</ul>
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
}
```
<ul>
<li><b>order:</b>
This is an object controlling the ordering of the results.
It contains a list of strings called <i>items</i> with the names of the properties to use.
<br/>If the name of the property is prefixed with a <b>'-'</b>, the ordering direction is <b>DESC</b>. Otherwise, it is <b>ASC</b>.
</li>
</ul>
For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object:
```JSON
{
"items": [
"-createdAt"
],
}
```
<ul>
<li><b>metadata:</b>
This is an object containing metadata for the request. There is only one available option.
<ul>
<li><b>countAll:</b>
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.
<br/>The first option is useful for the UI clients to calculate how many result pages are available.
</li>
</ul>
</li>
<li><b>project:</b>
This is an object controlling the data projection of the results.
It contains a list of strings called <i>fields</i> with the names of the properties to project.
<br/>You can also include properties that are deeper in the object tree by prefixing them with dots.
</li>
</ul>
### <u>Reference specific query parameters:</u>
<ul>
<li><b>like:</b>
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.
</li>
<li><b>typeId:</b>
This is the type id of the references we want in the response. <br/>If empty, every record is included.
</li>
<li><b>key:</b>
This is the id of the external source (API) we want results from.
<br/>If not present, no external reference is included.
</li>
<li><b>dependencyReferences:</b>
This is a list and determines which records we want to include in the response, based on the references they depend on.
<br/>If not present, every record is included.
</li>
</ul>
""";
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 { 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.<br/> It also allows to restrict the results using a query object passed in the request body.<br/>
"""; """;
public static final String endpoint_query_plan_associated =
"""
This endpoint is used to fetch all the available users.<br/>
It also allows to restrict the results using a query object passed in the request body.<br/>
""";
public static final String endpoint_query_request_body = public static final String endpoint_query_request_body =
""" """
Let's explore the options this object gives us. 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 = 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 { public static final class Principal {