Added swagger docs for users, principal and locks
This commit is contained in:
parent
a2540e5300
commit
bba4834e21
|
@ -1,10 +1,9 @@
|
|||
package org.opencdmp.query.lookup;
|
||||
|
||||
|
||||
import org.opencdmp.commons.enums.LockTargetType;
|
||||
import org.opencdmp.query.LockQuery;
|
||||
import gr.cite.tools.data.query.Lookup;
|
||||
import gr.cite.tools.data.query.QueryFactory;
|
||||
import org.opencdmp.commons.enums.LockTargetType;
|
||||
import org.opencdmp.query.LockQuery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -20,8 +19,8 @@ public class LockLookup extends Lookup {
|
|||
private List<LockTargetType> targetTypes;
|
||||
|
||||
private List<UUID> excludedIds;
|
||||
private List<UUID> userIds;
|
||||
|
||||
private List<UUID> userIds;
|
||||
|
||||
public String getLike() {
|
||||
return like;
|
||||
|
@ -73,12 +72,18 @@ public class LockLookup extends Lookup {
|
|||
|
||||
public LockQuery enrich(QueryFactory queryFactory) {
|
||||
LockQuery query = queryFactory.query(LockQuery.class);
|
||||
if (this.like != null) query.like(this.like);
|
||||
if (this.ids != null) query.ids(this.ids);
|
||||
if (this.targetIds != null) query.targetIds(this.targetIds);
|
||||
if (this.targetTypes != null) query.targetTypes(this.targetTypes);
|
||||
if (this.excludedIds != null) query.excludedIds(this.excludedIds);
|
||||
if (this.userIds != null) query.userIds(this.userIds);
|
||||
if (this.like != null)
|
||||
query.like(this.like);
|
||||
if (this.ids != null)
|
||||
query.ids(this.ids);
|
||||
if (this.targetIds != null)
|
||||
query.targetIds(this.targetIds);
|
||||
if (this.targetTypes != null)
|
||||
query.targetTypes(this.targetTypes);
|
||||
if (this.excludedIds != null)
|
||||
query.excludedIds(this.excludedIds);
|
||||
if (this.userIds != null)
|
||||
query.userIds(this.userIds);
|
||||
|
||||
this.enrichCommon(query);
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.opencdmp.query.lookup;
|
||||
|
||||
import org.opencdmp.commons.enums.IsActive;
|
||||
import org.opencdmp.query.UserQuery;
|
||||
import gr.cite.tools.data.query.Lookup;
|
||||
import gr.cite.tools.data.query.QueryFactory;
|
||||
import org.opencdmp.commons.enums.IsActive;
|
||||
import org.opencdmp.query.UserQuery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -13,11 +13,15 @@ public class UserLookup extends Lookup {
|
|||
private String like;
|
||||
|
||||
private List<UUID> ids;
|
||||
|
||||
private List<String> emails;
|
||||
|
||||
private List<UUID> excludedIds;
|
||||
|
||||
private List<IsActive> isActive;
|
||||
|
||||
private UserRoleLookup userRoleSubQuery;
|
||||
|
||||
private TenantUserLookup tenantUserSubQuery;
|
||||
|
||||
public String getLike() {
|
||||
|
@ -78,13 +82,20 @@ public class UserLookup extends Lookup {
|
|||
|
||||
public UserQuery enrich(QueryFactory queryFactory) {
|
||||
UserQuery query = queryFactory.query(UserQuery.class);
|
||||
if (this.like != null) query.like(this.like);
|
||||
if (this.ids != null) query.ids(this.ids);
|
||||
if (this.emails != null) query.emails(this.emails);
|
||||
if (this.userRoleSubQuery != null) query.userRoleSubQuery(this.userRoleSubQuery.enrich(queryFactory));
|
||||
if (this.tenantUserSubQuery != null) query.tenantUserSubQuery(this.tenantUserSubQuery.enrich(queryFactory));
|
||||
if (this.excludedIds != null) query.excludedIds(this.excludedIds);
|
||||
if (this.isActive != null) query.isActive(this.isActive);
|
||||
if (this.like != null)
|
||||
query.like(this.like);
|
||||
if (this.ids != null)
|
||||
query.ids(this.ids);
|
||||
if (this.emails != null)
|
||||
query.emails(this.emails);
|
||||
if (this.userRoleSubQuery != null)
|
||||
query.userRoleSubQuery(this.userRoleSubQuery.enrich(queryFactory));
|
||||
if (this.tenantUserSubQuery != null)
|
||||
query.tenantUserSubQuery(this.tenantUserSubQuery.enrich(queryFactory));
|
||||
if (this.excludedIds != null)
|
||||
query.excludedIds(this.excludedIds);
|
||||
if (this.isActive != null)
|
||||
query.isActive(this.isActive);
|
||||
|
||||
this.enrichCommon(query);
|
||||
|
||||
|
|
|
@ -12,6 +12,13 @@ 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.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.opencdmp.audit.AuditableAction;
|
||||
import org.opencdmp.authorization.AffiliatedResource;
|
||||
|
@ -19,6 +26,11 @@ import org.opencdmp.authorization.AuthorizationFlags;
|
|||
import org.opencdmp.authorization.Permission;
|
||||
import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver;
|
||||
import org.opencdmp.commons.enums.LockTargetType;
|
||||
import org.opencdmp.controllers.swagger.SwaggerHelpers;
|
||||
import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader;
|
||||
import org.opencdmp.controllers.swagger.annotation.Swagger400;
|
||||
import org.opencdmp.controllers.swagger.annotation.Swagger404;
|
||||
import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses;
|
||||
import org.opencdmp.data.LockEntity;
|
||||
import org.opencdmp.model.Lock;
|
||||
import org.opencdmp.model.LockStatus;
|
||||
|
@ -26,6 +38,7 @@ import org.opencdmp.model.builder.LockBuilder;
|
|||
import org.opencdmp.model.censorship.LockCensor;
|
||||
import org.opencdmp.model.persist.LockPersist;
|
||||
import org.opencdmp.model.result.QueryResult;
|
||||
import org.opencdmp.model.user.User;
|
||||
import org.opencdmp.query.LockQuery;
|
||||
import org.opencdmp.query.lookup.LockLookup;
|
||||
import org.opencdmp.service.lock.LockService;
|
||||
|
@ -49,6 +62,8 @@ import java.util.UUID;
|
|||
|
||||
@RestController
|
||||
@RequestMapping(path = "api/lock")
|
||||
@Tag(name = "Locks", description = "Manage locked entities")
|
||||
@SwaggerCommonErrorResponses
|
||||
public class LockController {
|
||||
|
||||
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(LockController.class));
|
||||
|
@ -66,7 +81,9 @@ public class LockController {
|
|||
private final MessageSource messageSource;
|
||||
|
||||
private final AuthorizationService authService;
|
||||
|
||||
private final AuthorizationContentResolver authorizationContentResolver;
|
||||
|
||||
@Autowired
|
||||
public LockController(BuilderFactory builderFactory,
|
||||
AuditService auditService,
|
||||
|
@ -85,6 +102,25 @@ public class LockController {
|
|||
}
|
||||
|
||||
@PostMapping("query")
|
||||
@OperationWithTenantHeader(summary = "Query all locked entities", description = SwaggerHelpers.Lock.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.Lock.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.Lock.endpoint_query_request_body_example
|
||||
)
|
||||
}
|
||||
)), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = Lock.class
|
||||
)
|
||||
),
|
||||
examples = @ExampleObject(
|
||||
name = "First page",
|
||||
description = "Example with the first page of paginated results",
|
||||
value = SwaggerHelpers.Lock.endpoint_query_response_example
|
||||
))))
|
||||
public QueryResult<Lock> query(@RequestBody LockLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
|
||||
logger.debug("querying {}", Lock.class.getSimpleName());
|
||||
|
||||
|
@ -101,7 +137,17 @@ public class LockController {
|
|||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
public Lock get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
@OperationWithTenantHeader(summary = "Fetch a specific lock by id", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Lock.class
|
||||
))
|
||||
))
|
||||
@Swagger404
|
||||
public Lock get(
|
||||
@Parameter(name = "id", description = "The id of a user to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
logger.debug(new MapLogEntry("retrieving" + Lock.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||
|
||||
this.censorFactory.censor(LockCensor.class).censor(fieldSet, null);
|
||||
|
@ -120,9 +166,20 @@ public class LockController {
|
|||
}
|
||||
|
||||
@PostMapping("persist")
|
||||
@OperationWithTenantHeader(summary = "Create a new or update an existing lock", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Lock.class
|
||||
))
|
||||
))
|
||||
@Swagger400
|
||||
@Swagger404
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = LockPersist.LockPersistValidator.ValidatorName, argumentName = "model")
|
||||
public Lock persist(@RequestBody LockPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
|
||||
public Lock persist(
|
||||
@RequestBody LockPersist model,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("persisting" + Lock.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
|
||||
this.censorFactory.censor(LockCensor.class).censor(fieldSet, null);
|
||||
|
||||
|
@ -137,7 +194,17 @@ public class LockController {
|
|||
}
|
||||
|
||||
@GetMapping("target/{id}")
|
||||
public Lock getWithTarget(@PathVariable("id") UUID targetId, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
@OperationWithTenantHeader(summary = "Fetch a specific lock by target id", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Lock.class
|
||||
))
|
||||
))
|
||||
@Swagger404
|
||||
public Lock getWithTarget(
|
||||
@Parameter(name = "id", description = "The target id of a lock to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID targetId,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
logger.debug(new MapLogEntry("retrieving" + Lock.class.getSimpleName()).And("targetId", targetId).And("fields", fieldSet));
|
||||
|
||||
this.censorFactory.censor(LockCensor.class).censor(fieldSet, null);
|
||||
|
@ -157,7 +224,16 @@ public class LockController {
|
|||
|
||||
@Transactional
|
||||
@GetMapping("target/status/{id}")
|
||||
public LockStatus getLocked(@PathVariable("id") UUID targetId, FieldSet fieldSet) throws Exception {
|
||||
@OperationWithTenantHeader(summary = "Fetch a lock status by target id", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = LockStatus.class
|
||||
))
|
||||
))
|
||||
public LockStatus getLocked(
|
||||
@Parameter(name = "id", description = "The target id of a lock to fetch the status", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID targetId,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws Exception {
|
||||
logger.debug(new MapLogEntry("is locked" + Lock.class.getSimpleName()).And("targetId", targetId).And("fields", fieldSet));
|
||||
this.authService.authorizeForce(Permission.BrowseLock);
|
||||
|
||||
|
@ -171,7 +247,16 @@ public class LockController {
|
|||
|
||||
@Transactional
|
||||
@GetMapping("target/lock/{id}/{targetType}")
|
||||
public boolean lock(@PathVariable("id") UUID targetId, @PathVariable("targetType") int targetType) throws Exception {
|
||||
@OperationWithTenantHeader(summary = "Lock a target", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Boolean.class
|
||||
))
|
||||
))
|
||||
public boolean lock(
|
||||
@Parameter(name = "id", description = "The target id to be locked", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID targetId,
|
||||
@Parameter(name = "targetType", description = "The target type to be locked", example = "0", required = true) @PathVariable("targetType") int targetType
|
||||
) throws Exception {
|
||||
AffiliatedResource affiliatedResourcePlan = this.authorizationContentResolver.planAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescription = this.authorizationContentResolver.descriptionAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescriptionTemplate = this.authorizationContentResolver.descriptionTemplateAffiliation(targetId);
|
||||
|
@ -187,7 +272,15 @@ public class LockController {
|
|||
|
||||
@Transactional
|
||||
@GetMapping("target/touch/{id}")
|
||||
public boolean touch(@PathVariable("id") UUID targetId) throws Exception {
|
||||
@OperationWithTenantHeader(summary = "Touch a locked target", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Boolean.class
|
||||
))
|
||||
))
|
||||
public boolean touch(
|
||||
@Parameter(name = "id", description = "The target id to be touched", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID targetId
|
||||
) throws Exception {
|
||||
AffiliatedResource affiliatedResourcePlan = this.authorizationContentResolver.planAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescription = this.authorizationContentResolver.descriptionAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescriptionTemplate = this.authorizationContentResolver.descriptionTemplateAffiliation(targetId);
|
||||
|
@ -202,7 +295,15 @@ public class LockController {
|
|||
|
||||
@Transactional
|
||||
@DeleteMapping("target/unlock/{id}")
|
||||
public boolean unlock(@PathVariable("id") UUID targetId) throws Exception {
|
||||
@OperationWithTenantHeader(summary = "Unlock a target", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = Boolean.class
|
||||
))
|
||||
))
|
||||
public boolean unlock(
|
||||
@Parameter(name = "id", description = "The target id to be unlocked", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID targetId
|
||||
) throws Exception {
|
||||
AffiliatedResource affiliatedResourcePlan = this.authorizationContentResolver.planAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescription = this.authorizationContentResolver.descriptionAffiliation(targetId);
|
||||
AffiliatedResource affiliatedResourceDescriptionTemplate = this.authorizationContentResolver.descriptionTemplateAffiliation(targetId);
|
||||
|
@ -216,8 +317,13 @@ public class LockController {
|
|||
}
|
||||
|
||||
@DeleteMapping("{id}/{target}")
|
||||
@OperationWithTenantHeader(summary = "Delete a lock by id and target", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public void delete(@PathVariable("id") UUID id, @PathVariable("target") UUID target) throws MyForbiddenException, InvalidApplicationException {
|
||||
public void delete(
|
||||
@Parameter(name = "id", description = "The id of the lock to be deleted", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
|
||||
@Parameter(name = "target", description = "The target id of the lock", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("target") UUID target
|
||||
) throws MyForbiddenException, InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("retrieving" + Lock.class.getSimpleName()).And("id", id));
|
||||
|
||||
this.lockService.deleteAndSave(id, target);
|
||||
|
|
|
@ -502,7 +502,7 @@ public class PlanController {
|
|||
public PreprocessingPlanModel preprocessing(
|
||||
@RequestParam("fileId") UUID fileId,
|
||||
@RequestParam("repositoryId") String repositoryId
|
||||
) throws InvalidAlgorithmParameterException, JAXBException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
|
||||
) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
|
||||
logger.debug(new MapLogEntry("preprocessing plan" + Plan.class.getSimpleName()).And("transformerId", repositoryId).And("fileId", fileId));
|
||||
|
||||
PreprocessingPlanModel model = this.planService.preprocessingPlan(fileId, repositoryId);
|
||||
|
|
|
@ -6,7 +6,15 @@ import gr.cite.tools.auditing.AuditService;
|
|||
import gr.cite.tools.fieldset.BaseFieldSet;
|
||||
import gr.cite.tools.fieldset.FieldSet;
|
||||
import gr.cite.tools.logging.LoggerService;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
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.model.Tenant;
|
||||
import org.opencdmp.models.Account;
|
||||
import org.opencdmp.models.AccountBuilder;
|
||||
|
@ -23,6 +31,7 @@ import java.util.List;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/principal/")
|
||||
@Tag(name = "Principal", description = "Get user account information")
|
||||
public class PrincipalController {
|
||||
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
|
||||
private final AuditService auditService;
|
||||
|
@ -43,7 +52,15 @@ public class PrincipalController {
|
|||
}
|
||||
|
||||
@RequestMapping(path = "me", method = RequestMethod.GET )
|
||||
public Account me(FieldSet fieldSet) throws InvalidApplicationException {
|
||||
@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()) {
|
||||
|
@ -80,7 +97,11 @@ public class PrincipalController {
|
|||
}
|
||||
|
||||
@GetMapping("my-tenants")
|
||||
public List<Tenant> myTenants(FieldSet fieldSet) {
|
||||
@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);
|
||||
|
|
|
@ -12,11 +12,24 @@ 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;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import org.opencdmp.audit.AuditableAction;
|
||||
import org.opencdmp.authorization.AuthorizationFlags;
|
||||
import org.opencdmp.commons.enums.IsActive;
|
||||
import org.opencdmp.commons.scope.user.UserScope;
|
||||
import org.opencdmp.controllers.swagger.SwaggerHelpers;
|
||||
import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader;
|
||||
import org.opencdmp.controllers.swagger.annotation.Swagger400;
|
||||
import org.opencdmp.controllers.swagger.annotation.Swagger404;
|
||||
import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses;
|
||||
import org.opencdmp.data.UserEntity;
|
||||
import org.opencdmp.model.PlanAssociatedUser;
|
||||
import org.opencdmp.model.UserRole;
|
||||
|
@ -26,6 +39,7 @@ import org.opencdmp.model.censorship.PlanAssociatedUserCensor;
|
|||
import org.opencdmp.model.censorship.UserCensor;
|
||||
import org.opencdmp.model.persist.*;
|
||||
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;
|
||||
|
@ -51,6 +65,8 @@ import java.util.UUID;
|
|||
|
||||
@RestController
|
||||
@RequestMapping(path = "api/user")
|
||||
@Tag(name = "Users", description = "Manage users")
|
||||
@SwaggerCommonErrorResponses
|
||||
public class UserController {
|
||||
|
||||
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserController.class));
|
||||
|
@ -91,6 +107,25 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("query")
|
||||
@OperationWithTenantHeader(summary = "Query all users", description = SwaggerHelpers.User.endpoint_query, 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_request_body_example
|
||||
)
|
||||
}
|
||||
)), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
)
|
||||
),
|
||||
examples = @ExampleObject(
|
||||
name = "First page",
|
||||
description = "Example with the first page of paginated results",
|
||||
value = SwaggerHelpers.User.endpoint_query_response_example
|
||||
))))
|
||||
public QueryResult<User> query(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException {
|
||||
logger.debug("querying {}", User.class.getSimpleName());
|
||||
|
||||
|
@ -108,6 +143,7 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("plan-associated/query")
|
||||
@Hidden
|
||||
public QueryResult<PlanAssociatedUser> queryPlanAssociated(@RequestBody UserLookup lookup) throws MyApplicationException, MyForbiddenException {
|
||||
logger.debug("querying {}", User.class.getSimpleName());
|
||||
|
||||
|
@ -125,7 +161,17 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
public User get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
@OperationWithTenantHeader(summary = "Fetch a specific user by id", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
))
|
||||
))
|
||||
@Swagger404
|
||||
public User get(
|
||||
@Parameter(name = "id", description = "The id of a user to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||
|
||||
this.censorFactory.censor(UserCensor.class).censor(fieldSet, id);
|
||||
|
@ -144,7 +190,17 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("/by-email/{email}")
|
||||
public User get(@PathVariable("email") String email, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
@OperationWithTenantHeader(summary = "Fetch a specific user by email", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
))
|
||||
))
|
||||
@Swagger404
|
||||
public User get(
|
||||
@Parameter(name = "email", description = "The email of a user to fetch", example = "admin@cite.gr", required = true) @PathVariable("email") String email,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("email", email).And("fields", fieldSet));
|
||||
|
||||
this.censorFactory.censor(UserCensor.class).censor(fieldSet, null);
|
||||
|
@ -163,6 +219,7 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("/export/csv/{hasTenantAdminMode}")
|
||||
@Hidden
|
||||
public ResponseEntity<byte[]> exportCsv(@PathVariable("hasTenantAdminMode") Boolean hasTenantAdminMode) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("export" + User.class.getSimpleName()).And("hasTenantAdminMode", hasTenantAdminMode));
|
||||
|
||||
|
@ -177,7 +234,16 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine")
|
||||
public User getMine(FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
|
||||
@OperationWithTenantHeader(summary = "Fetch information for the logged in user", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
))
|
||||
))
|
||||
@Swagger404
|
||||
public User getMine(
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("retrieving me" + User.class.getSimpleName()).And("fields", fieldSet));
|
||||
|
||||
this.censorFactory.censor(UserCensor.class).censor(fieldSet, this.userScope.getUserId());
|
||||
|
@ -195,8 +261,12 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/language/{language}")
|
||||
@OperationWithTenantHeader(summary = "Update the language for the logged in user", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public void updateLanguageMine(@PathVariable("language") String language) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
public void updateLanguageMine(
|
||||
@Parameter(name = "language", description = "The updated language", example = "en", required = true) @PathVariable("language") String language
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("language", language));
|
||||
this.userTypeService.updateLanguageMine(language);
|
||||
|
||||
|
@ -206,8 +276,12 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/timezone/{timezone}")
|
||||
@OperationWithTenantHeader(summary = "Update the timezone for the logged in user", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public void updateTimezoneMine(@PathVariable("timezone") String timezone) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
public void updateTimezoneMine(
|
||||
@Parameter(name = "timezone", description = "The updated timezone", example = "Europe/Budapest", required = true) @PathVariable("timezone") String timezone
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("timezone", timezone));
|
||||
this.userTypeService.updateTimezoneMine(timezone);
|
||||
|
||||
|
@ -217,8 +291,12 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/culture/{culture}")
|
||||
@OperationWithTenantHeader(summary = "Update the culture for the logged in user", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public void updateCultureMine(@PathVariable("culture") String culture) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
public void updateCultureMine(
|
||||
@Parameter(name = "culture", description = "The updated culture", example = "en-US", required = true) @PathVariable("culture") String culture
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException {
|
||||
logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("culture", culture));
|
||||
this.userTypeService.updateCultureMine(culture);
|
||||
|
||||
|
@ -228,9 +306,20 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("persist")
|
||||
@OperationWithTenantHeader(summary = "Create a new or update an existing user", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
))
|
||||
))
|
||||
@Swagger400
|
||||
@Swagger404
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = UserPersist.UserPersistValidator.ValidatorName, argumentName = "model")
|
||||
public User persist(@RequestBody UserPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException {
|
||||
public User persist(
|
||||
@RequestBody UserPersist model,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException {
|
||||
logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
|
||||
User persisted = this.userTypeService.persist(model, fieldSet);
|
||||
|
||||
|
@ -243,9 +332,20 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("persist/roles")
|
||||
@OperationWithTenantHeader(summary = "Update user roles", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = User.class
|
||||
))
|
||||
))
|
||||
@Swagger400
|
||||
@Swagger404
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = UserRolePatchPersist.UserRolePatchPersistValidator.ValidatorName, argumentName = "model")
|
||||
public User persistRoles(@RequestBody UserRolePatchPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException {
|
||||
public User persistRoles(
|
||||
@RequestBody UserRolePatchPersist model,
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException {
|
||||
logger.debug(new MapLogEntry("persisting" + UserRole.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
|
||||
User persisted = this.userTypeService.patchRoles(model, fieldSet);
|
||||
|
||||
|
@ -258,8 +358,13 @@ public class UserController {
|
|||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@OperationWithTenantHeader(summary = "Delete a user by id", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Swagger404
|
||||
@Transactional
|
||||
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
|
||||
public void delete(
|
||||
@Parameter(name = "id", description = "The id of a user to delete", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id
|
||||
) throws MyForbiddenException, InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("retrieving" + User.class.getSimpleName()).And("id", id));
|
||||
|
||||
this.userTypeService.deleteAndSave(id);
|
||||
|
@ -268,6 +373,10 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("mine/merge-account-request")
|
||||
@OperationWithTenantHeader(summary = "Merge user accounts", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Swagger400
|
||||
@Swagger404
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = UserMergeRequestPersist.UserMergeRequestPersistValidator.ValidatorName, argumentName = "model")
|
||||
public Boolean mergeAccount(@RequestBody UserMergeRequestPersist model) throws InvalidApplicationException, JAXBException {
|
||||
|
@ -283,8 +392,12 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/confirm-merge-account/token/{token}")
|
||||
@OperationWithTenantHeader(summary = "Confirm the merge of user accounts", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public Boolean confirmMergeAccount(@PathVariable("token") String token) throws InvalidApplicationException, IOException {
|
||||
public Boolean confirmMergeAccount(
|
||||
@Parameter(name = "token", description = "The token for the action", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("token") String token
|
||||
) throws InvalidApplicationException, IOException {
|
||||
logger.debug(new MapLogEntry("confirm merge account to user").And("token", token));
|
||||
|
||||
this.userTypeService.confirmMergeAccount(token);
|
||||
|
@ -297,7 +410,11 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/allow-merge-account/token/{token}")
|
||||
public Boolean getUserTokenPermission(@PathVariable("token") String token) throws InvalidApplicationException, IOException {
|
||||
@OperationWithTenantHeader(summary = "Allow the merge of user accounts", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
public Boolean getUserTokenPermission(
|
||||
@Parameter(name = "token", description = "The token for the action", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("token") String token
|
||||
) throws InvalidApplicationException, IOException {
|
||||
logger.debug(new MapLogEntry("allow merge account to user").And("token", token));
|
||||
|
||||
this.auditService.track(AuditableAction.User_AllowMergeAccount);
|
||||
|
@ -306,6 +423,9 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("mine/remove-credential-request")
|
||||
@OperationWithTenantHeader(summary = "Remove user credentials", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Swagger400
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.ValidatorName, argumentName = "model")
|
||||
public Boolean removeCredentialAccount(@RequestBody RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException {
|
||||
|
@ -321,8 +441,12 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("mine/confirm-remove-credential/token/{token}")
|
||||
@OperationWithTenantHeader(summary = "Confirm the removal of user credentials", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Transactional
|
||||
public Boolean confirmRemoveCredentialAccount(@PathVariable("token") String token) throws InvalidApplicationException, JAXBException {
|
||||
public Boolean confirmRemoveCredentialAccount(
|
||||
@Parameter(name = "token", description = "The token for the action", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("token") String token
|
||||
) throws InvalidApplicationException, JAXBException {
|
||||
logger.debug(new MapLogEntry("confirm remove credential to user").And("token", token));
|
||||
|
||||
this.userTypeService.confirmRemoveCredential(token);
|
||||
|
@ -335,6 +459,9 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("invite-users-to-tenant")
|
||||
@OperationWithTenantHeader(summary = "Invite users to a tenant", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Swagger400
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = UserTenantUsersInviteRequest.UserTenantUsersInviteRequestValidator.ValidatorName, argumentName = "model")
|
||||
public Boolean inviteUsersToTenant(@RequestBody UserTenantUsersInviteRequest users) throws InvalidApplicationException, JAXBException {
|
||||
|
@ -350,8 +477,13 @@ public class UserController {
|
|||
}
|
||||
|
||||
@GetMapping("confirm-invite-user-to-tenant/token/{token}")
|
||||
@OperationWithTenantHeader(summary = "Confirm user tenant invitation", description = "",
|
||||
responses = @ApiResponse(description = "OK", responseCode = "200"))
|
||||
@Swagger400
|
||||
@Transactional
|
||||
public Boolean confirmInviteUserToTenant(@PathVariable("token") String token) throws InvalidApplicationException {
|
||||
public Boolean confirmInviteUserToTenant(
|
||||
@Parameter(name = "token", description = "The token for the action", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("token") String token
|
||||
) throws InvalidApplicationException {
|
||||
logger.debug(new MapLogEntry("confirm merge account to user").And("token", token));
|
||||
|
||||
this.userTypeService.confirmUserInviteToTenant(token);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.opencdmp.controllers.swagger;
|
||||
|
||||
public class SwaggerHelpers {
|
||||
public final class SwaggerHelpers {
|
||||
|
||||
public static class Commons {
|
||||
public static final class Commons {
|
||||
|
||||
public static final String fieldset_description =
|
||||
"""
|
||||
|
@ -10,7 +10,7 @@ public class SwaggerHelpers {
|
|||
""";
|
||||
}
|
||||
|
||||
public static class Errors {
|
||||
public static final class Errors {
|
||||
|
||||
public static final String message_400 =
|
||||
"""
|
||||
|
@ -51,7 +51,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class Plan {
|
||||
public static final class Plan {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -905,7 +905,7 @@ public class SwaggerHelpers {
|
|||
""";
|
||||
}
|
||||
|
||||
public static class Description {
|
||||
public static final class Description {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -1760,7 +1760,7 @@ public class SwaggerHelpers {
|
|||
""";
|
||||
}
|
||||
|
||||
public static class DescriptionTemplate {
|
||||
public static final class DescriptionTemplate {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -2144,7 +2144,7 @@ public class SwaggerHelpers {
|
|||
""";
|
||||
}
|
||||
|
||||
public static class DescriptionTemplateType {
|
||||
public static final class DescriptionTemplateType {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -2307,7 +2307,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class PlanBlueprint {
|
||||
public static final class PlanBlueprint {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -2548,7 +2548,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class FileTransformer {
|
||||
public static final class FileTransformer {
|
||||
|
||||
public static final String endpoint_get_available_transformers =
|
||||
"""
|
||||
|
@ -2569,7 +2569,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class EntityDoi {
|
||||
public static final class EntityDoi {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -2835,7 +2835,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class Deposit {
|
||||
public static final class Deposit {
|
||||
|
||||
public static final String endpoint_get_available_repos =
|
||||
"""
|
||||
|
@ -2859,7 +2859,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class Tag {
|
||||
public static final class Tag {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -3086,7 +3086,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class Reference {
|
||||
public static final class Reference {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -3348,7 +3348,7 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static class ReferenceType {
|
||||
public static final class ReferenceType {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
|
@ -3579,4 +3579,672 @@ public class SwaggerHelpers {
|
|||
|
||||
}
|
||||
|
||||
public static final class Lock {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
This endpoint is used to fetch all the current entity locks.<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.
|
||||
|
||||
### <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>Lock specific query parameters:</u>
|
||||
|
||||
<ul>
|
||||
<li><b>like:</b>
|
||||
If there is a like parameter present in the query, only the locks locking the provided target id will be in the response.
|
||||
</li>
|
||||
<li><b>ids:</b>
|
||||
This is a list and contains the ids we want to include in the response. <br/>If empty, every record is included.
|
||||
</li>
|
||||
<li><b>excludedIds:</b>
|
||||
This is a list and contains the ids we want to exclude from the response. <br/>If empty, no record gets excluded.
|
||||
</li>
|
||||
<li><b>targetIds:</b>
|
||||
This is a list and contains the ids of the locked targets of the locks we want to include in the response. <br/>If empty, every record is included.
|
||||
</li>
|
||||
<li><b>userIds:</b>
|
||||
This is a list and contains the ids of the users of the locks we want to include in the response. <br/>If empty, every record is included.
|
||||
</li>
|
||||
<li><b>targetTypes:</b>
|
||||
This is a list and determines which records we want to include in the response, based on their target type.
|
||||
The target type can be <i>Plan</i>, <i>Description</i>, <i>PlanBlueprint</i> or <i>DescriptionTemplate</i>. We add 0, 1, 2 or 3 to the list respectively.
|
||||
<br/>If not present, every record is included.
|
||||
</li>
|
||||
</ul>
|
||||
""";
|
||||
|
||||
public static final String endpoint_query_request_body_example =
|
||||
"""
|
||||
{
|
||||
"project":{
|
||||
"fields":[
|
||||
"id",
|
||||
"target",
|
||||
"targetType",
|
||||
"lockedAt",
|
||||
"lockedBy.name",
|
||||
"touchedAt",
|
||||
"hash",
|
||||
"belongsToCurrentTenant"
|
||||
]
|
||||
},
|
||||
"metadata":{
|
||||
"countAll":true
|
||||
},
|
||||
"page":{
|
||||
"offset":0,
|
||||
"size":10
|
||||
},
|
||||
"isActive":[
|
||||
1
|
||||
],
|
||||
"order":{
|
||||
"items":[
|
||||
"-lockedAt"
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
public static final String endpoint_query_response_example =
|
||||
"""
|
||||
{
|
||||
"items":[
|
||||
{
|
||||
"id":"d0423a02-abe8-4210-8ee3-504deb79d8c6",
|
||||
"target":"39fabf73-546c-49a3-8789-6401f65d56b6",
|
||||
"targetType":1,
|
||||
"lockedBy":{
|
||||
"name":"admin admin"
|
||||
},
|
||||
"lockedAt":"2024-06-28T08:47:03.784241Z",
|
||||
"touchedAt":"2024-07-04T11:01:10.762955Z",
|
||||
"hash":"1720090870",
|
||||
"belongsToCurrentTenant":true
|
||||
},
|
||||
{
|
||||
"id":"74b2ae7e-0a28-4ebc-aec9-3f849ccb3e60",
|
||||
"target":"37701076-e0ff-4e4f-95aa-9f3d6a23083a",
|
||||
"targetType":2,
|
||||
"lockedBy":{
|
||||
"name":"admin admin"
|
||||
},
|
||||
"lockedAt":"2024-07-04T08:29:46.591493Z",
|
||||
"touchedAt":"2024-07-04T08:33:13.451444Z",
|
||||
"hash":"1720081993",
|
||||
"belongsToCurrentTenant":true
|
||||
},
|
||||
{
|
||||
"id":"642d6756-bf62-4555-9ca5-bec50c5cdb85",
|
||||
"target":"212342fe-ab6f-4604-b80e-ac23aca93c76",
|
||||
"targetType":3,
|
||||
"lockedBy":{
|
||||
"name":"admin admin"
|
||||
},
|
||||
"lockedAt":"2024-07-02T11:36:17.926775Z",
|
||||
"touchedAt":"2024-07-02T11:39:24.278433Z",
|
||||
"hash":"1719920364",
|
||||
"belongsToCurrentTenant":true
|
||||
},
|
||||
{
|
||||
"id":"4a7fd0b4-1b2e-4148-a5fc-152f73caa7e5",
|
||||
"target":"0e58f8b7-a91e-432a-8de2-edf03679c313",
|
||||
"targetType":1,
|
||||
"lockedBy":{
|
||||
"name":"admin"
|
||||
},
|
||||
"lockedAt":"2024-07-01T11:16:14.806474Z",
|
||||
"touchedAt":"2024-07-01T11:23:21.922533Z",
|
||||
"hash":"1719833001",
|
||||
"belongsToCurrentTenant":true
|
||||
}
|
||||
],
|
||||
"count":4
|
||||
}
|
||||
""";
|
||||
|
||||
}
|
||||
|
||||
public static final class User {
|
||||
|
||||
public static final String endpoint_query =
|
||||
"""
|
||||
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.
|
||||
|
||||
### <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>User specific query parameters:</u>
|
||||
|
||||
<ul>
|
||||
<li><b>like:</b>
|
||||
If there is a like parameter present in the query, only the users that include the contents of the parameter in their names will be in the response.
|
||||
</li>
|
||||
<li><b>ids:</b>
|
||||
This is a list and contains the ids we want to include in the response. <br/>If empty, every record is included.
|
||||
</li>
|
||||
<li><b>excludedIds:</b>
|
||||
This is a list and contains the ids we want to exclude from the response. <br/>If empty, no record gets excluded.
|
||||
</li>
|
||||
<li><b>isActive:</b>
|
||||
This is a list and determines which records we want to include in the response, based on if they are deleted or not.
|
||||
This filter works like this. If we want to view only the active records we pass [1] and for only the deleted records we pass [0].
|
||||
<br/>If not present or if we pass [0,1], every record is included.
|
||||
</li>
|
||||
<li><b>emails:</b>
|
||||
This is a list and determines which records we want to include in the response, based on their emails.
|
||||
<br/>If not present, every record is included.
|
||||
</li>
|
||||
</ul>
|
||||
""";
|
||||
|
||||
public static final String endpoint_query_request_body_example =
|
||||
"""
|
||||
{
|
||||
"project":{
|
||||
"fields":[
|
||||
"id",
|
||||
"name",
|
||||
"contacts.id",
|
||||
"contacts.type",
|
||||
"contacts.value",
|
||||
"globalRoles.id",
|
||||
"globalRoles.role",
|
||||
"tenantRoles.id",
|
||||
"tenantRoles.role",
|
||||
"additionalInfo.avatarUrl",
|
||||
"updatedAt",
|
||||
"createdAt",
|
||||
"hash",
|
||||
"isActive"
|
||||
]
|
||||
},
|
||||
"metadata":{
|
||||
"countAll":true
|
||||
},
|
||||
"page":{
|
||||
"offset":0,
|
||||
"size":10
|
||||
},
|
||||
"isActive":[
|
||||
1
|
||||
],
|
||||
"order":{
|
||||
"items":[
|
||||
"-createdAt"
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
public static final String endpoint_query_response_example =
|
||||
"""
|
||||
{
|
||||
"items":[
|
||||
{
|
||||
"id":"fc97ad11-2c73-4fc4-835e-e1ca0cf7b918",
|
||||
"name":"user5 user5",
|
||||
"createdAt":"2024-07-03T09:59:05.005425Z",
|
||||
"updatedAt":"2024-07-03T09:59:05.005425Z",
|
||||
"isActive":1,
|
||||
"hash":"1720000745",
|
||||
"additionalInfo":{
|
||||
\s
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"8b0972d2-e9d8-4c61-b69b-907ee452dade",
|
||||
"value":"user5@user5.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalRoles":[
|
||||
{
|
||||
"id":"50b8e8e8-eb02-40cb-aa6f-a99eb0f48721",
|
||||
"role":"User",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"tenantRoles":[
|
||||
{
|
||||
"id":"cc0307e5-4421-4e46-b7a1-b1326e6e786b",
|
||||
"role":"TenantUser",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"b494c989-60f1-4584-bc52-b56c40c66ade",
|
||||
"name":"installationadmin installationadmin",
|
||||
"createdAt":"2024-06-28T13:23:24.957340Z",
|
||||
"updatedAt":"2024-06-28T13:23:24.957340Z",
|
||||
"isActive":1,
|
||||
"hash":"1719581004",
|
||||
"additionalInfo":{
|
||||
\s
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"cd281a02-f851-400f-ba7f-391e16884051",
|
||||
"value":"installationadmin@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalRoles":[
|
||||
{
|
||||
"id":"1d4088c3-7d42-4227-93d9-ee46ead5f500",
|
||||
"role":"InstallationAdmin",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"tenantRoles":[
|
||||
{
|
||||
"id":"b606b428-f404-4232-81fa-a313fcbab25a",
|
||||
"role":"TenantUser",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"5119e0a6-53ee-4cad-ae58-aa3c1fc79683",
|
||||
"name":"user3 user3",
|
||||
"createdAt":"2024-06-28T11:23:22.962858Z",
|
||||
"updatedAt":"2024-06-28T11:23:22.962858Z",
|
||||
"isActive":1,
|
||||
"hash":"1719573802",
|
||||
"additionalInfo":{
|
||||
\s
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"18f1f3f2-77d6-4258-a7d0-1623d7282b82",
|
||||
"value":"user3@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalRoles":[
|
||||
{
|
||||
"id":"116ec22c-3b3a-44ae-9de0-be31c8d621c2",
|
||||
"role":"User",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"tenantRoles":[
|
||||
{
|
||||
"id":"618b266b-ecdb-46cd-a5c5-686dc76bba12",
|
||||
"role":"TenantUser",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"d1873841-3ae3-4a1c-8cfc-841327552313",
|
||||
"name":"dmproot dmproot",
|
||||
"createdAt":"2024-06-28T07:19:37.150149Z",
|
||||
"updatedAt":"2024-06-28T07:19:37.150149Z",
|
||||
"isActive":1,
|
||||
"hash":"1719559177",
|
||||
"additionalInfo":{
|
||||
\s
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"20d108c6-8277-40d0-a2b8-e6d8c9c332f0",
|
||||
"value":"dmproot@cite.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalRoles":[
|
||||
{
|
||||
"id":"209b7b67-5374-4b11-a1cf-ecfba6da5f16",
|
||||
"role":"Admin",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
},
|
||||
{
|
||||
"id":"5d7ee923-5456-456c-b46f-b53458b0e10a",
|
||||
"role":"User",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"tenantRoles":[
|
||||
{
|
||||
"id":"4a14b1fd-2e35-4817-89fa-9d8bf741aee7",
|
||||
"role":"TenantUser",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"890667ae-7efd-49d9-8ab5-3d48b84a48d1",
|
||||
"name":"admin admin",
|
||||
"createdAt":"2024-06-28T07:07:18.589432Z",
|
||||
"updatedAt":"2024-06-28T07:07:18.589432Z",
|
||||
"isActive":1,
|
||||
"hash":"1719558438",
|
||||
"additionalInfo":{
|
||||
\s
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"9739739b-b5d9-4e13-bc47-637b3760b340",
|
||||
"value":"admin@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalRoles":[
|
||||
{
|
||||
"id":"718dd2d0-cbda-4b33-b8be-a25ec30a53f3",
|
||||
"role":"User",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
},
|
||||
{
|
||||
"id":"c2c4129e-dcec-448a-8e7c-e4dd7cbc3d9d",
|
||||
"role":"Admin",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
],
|
||||
"tenantRoles":[
|
||||
{
|
||||
"id":"6961542d-6ea7-4064-a0ad-20d82896b9de",
|
||||
"role":"TenantAdmin",
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"1f709343-353b-4787-a8e0-f71020b53f94",
|
||||
"name":"user5847",
|
||||
"createdAt":"2024-06-26T22:46:43Z",
|
||||
"updatedAt":"2024-06-26T22:46:43Z",
|
||||
"isActive":1,
|
||||
"hash":"1719442003",
|
||||
"additionalInfo":{
|
||||
"avatarUrl":"null"
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"b8c17fa6-c8e1-49d8-9c7b-173dad43d995",
|
||||
"value":"user5847@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"ac633bd9-ac0b-4258-adc5-24246e1dacbc",
|
||||
"name":"user5846",
|
||||
"createdAt":"2024-06-26T22:08:44Z",
|
||||
"updatedAt":"2024-06-26T22:08:44Z",
|
||||
"isActive":1,
|
||||
"hash":"1719439724",
|
||||
"additionalInfo":{
|
||||
"avatarUrl":"null"
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"0126a7d6-c2f3-4f2e-85cc-9c999d74fa85",
|
||||
"value":"user5846@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"2b4e633b-5451-46fb-a1ad-34c1f8fb8fe7",
|
||||
"name":"user5844",
|
||||
"createdAt":"2024-06-26T17:04:01Z",
|
||||
"updatedAt":"2024-06-26T17:04:01Z",
|
||||
"isActive":1,
|
||||
"hash":"1719421441",
|
||||
"additionalInfo":{
|
||||
"avatarUrl":"null"
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"138b9b60-8bfb-4900-8f0f-32dd70c8841c",
|
||||
"value":"user5844@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"d9d0ae8b-8037-403e-a155-28d2387d6d7f",
|
||||
"name":"user5842",
|
||||
"createdAt":"2024-06-26T13:38:58Z",
|
||||
"updatedAt":"2024-06-26T13:38:58Z",
|
||||
"isActive":1,
|
||||
"hash":"1719409138",
|
||||
"additionalInfo":{
|
||||
"avatarUrl":"null"
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"12bb3f67-941c-4731-8de0-b8d43ff6e17a",
|
||||
"value":"user5842@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"3748e22c-3760-4ada-88ef-8addd247fc76",
|
||||
"name":"user5841",
|
||||
"createdAt":"2024-06-26T13:19:52Z",
|
||||
"updatedAt":"2024-06-26T13:22:19Z",
|
||||
"isActive":1,
|
||||
"hash":"1719408139",
|
||||
"additionalInfo":{
|
||||
"avatarUrl":"null"
|
||||
},
|
||||
"contacts":[
|
||||
{
|
||||
"id":"d536278f-9714-43f8-846c-cb5c6bcf2f59",
|
||||
"value":"user5841@dmp.gr",
|
||||
"type":0,
|
||||
"ordinal":0,
|
||||
"user":{
|
||||
\s
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"count":5821
|
||||
}
|
||||
""";
|
||||
|
||||
}
|
||||
|
||||
public static final class Principal {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ springdoc:
|
|||
displayName: Current
|
||||
packagesToScan: org.opencdmp.controllers
|
||||
packagesToExclude: org.opencdmp.controllers.publicapi
|
||||
pathsToMatch: "/api/plan/**, /api/description/**, /api/description-template/**, /api/description-template-type/**, /api/plan-blueprint/**, /api/entity-doi/**, /api/deposit/**, /api/file-transformer/**, /api/tag/**, /api/reference/**, /api/reference-type/**"
|
||||
pathsToMatch: "/api/plan/**, /api/description/**, /api/description-template/**, /api/description-template-type/**, /api/plan-blueprint/**, /api/entity-doi/**, /api/deposit/**, /api/file-transformer/**, /api/tag/**, /api/reference/**, /api/reference-type/**, /api/lock/**, /api/user/**, /api/principal/**"
|
||||
swaggerUi:
|
||||
enabled: true
|
||||
useRootPath: true
|
||||
|
|
Loading…
Reference in New Issue