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.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<Tenant> myTenants(
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) {
logger.debug("my-tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<Tenant> 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<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.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<Reference> searchReferenceWithDefinition(@RequestBody ReferenceSearchLookup lookup) throws MyNotFoundException, InvalidApplicationException {
logger.debug("search with db definition {}", Reference.class.getSimpleName());
@ -138,7 +152,7 @@ public class ReferenceController {
List<Reference> references = this.referenceService.searchReferenceData(lookup);
this.auditService.track(AuditableAction.Reference_Search, "lookup", lookup);
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.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<PlanAssociatedUser> 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<byte[]> 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<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));
// 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 {
@ -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/>
""";
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 =
"""
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 {