diff --git a/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java b/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java index c424d430b..e670c8918 100644 --- a/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java +++ b/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java @@ -2,7 +2,6 @@ package org.opencdmp.configurations; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java index 9f852d5e9..16ab7bbac 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java @@ -12,9 +12,7 @@ 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.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; @@ -28,10 +26,10 @@ import org.opencdmp.commons.enums.DmpAccessType; import org.opencdmp.commons.enums.DmpStatus; import org.opencdmp.commons.enums.IsActive; 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.controllers.swagger.annotation.OperationWithTenantHeader; import org.opencdmp.convention.ConventionService; import org.opencdmp.data.StorageFileEntity; import org.opencdmp.model.DescriptionValidationResult; @@ -96,18 +94,23 @@ public class DescriptionController { private final QueryFactory queryFactory; private final MessageSource messageSource; + private final ElasticQueryHelperService elasticQueryHelperService; + private final StorageFileService storageFileService; + private final ConventionService conventionService; + private final FieldSetExpanderService fieldSetExpanderService; + public DescriptionController( - BuilderFactory builderFactory, - AuditService auditService, - DescriptionService descriptionService, - CensorFactory censorFactory, - QueryFactory queryFactory, - MessageSource messageSource, - ElasticQueryHelperService elasticQueryHelperService, StorageFileService storageFileService, ConventionService conventionService, FieldSetExpanderService fieldSetExpanderService) { + BuilderFactory builderFactory, + AuditService auditService, + DescriptionService descriptionService, + CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource, + ElasticQueryHelperService elasticQueryHelperService, StorageFileService storageFileService, ConventionService conventionService, FieldSetExpanderService fieldSetExpanderService) { this.builderFactory = builderFactory; this.auditService = auditService; this.descriptionService = descriptionService; @@ -115,9 +118,9 @@ public class DescriptionController { this.queryFactory = queryFactory; this.messageSource = messageSource; this.elasticQueryHelperService = elasticQueryHelperService; - this.storageFileService = storageFileService; - this.conventionService = conventionService; - this.fieldSetExpanderService = fieldSetExpanderService; + this.storageFileService = storageFileService; + this.conventionService = conventionService; + this.fieldSetExpanderService = fieldSetExpanderService; } @PostMapping("public/query") @@ -130,7 +133,7 @@ public class DescriptionController { //DescriptionQuery query = lookup.enrich(this.queryFactory).authorize(EnumSet.of(Public)).dmpSubQuery(this.queryFactory.query(DmpQuery.class).isActive(IsActive.Active).statuses(DmpStatus.Finalized).accessTypes(DmpAccessType.Public)); QueryResult queryResult = this.elasticQueryHelperService.collectPublic(lookup, EnumSet.of(Public), null); - + this.auditService.track(AuditableAction.Description_PublicQuery, "lookup", lookup); return queryResult; @@ -142,13 +145,14 @@ public class DescriptionController { public PublicDescription publicGet(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + PublicDescription.class.getSimpleName()).And("id", id).And("fields", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); - + this.censorFactory.censor(PublicDescriptionCensor.class).censor(fieldSet); - DescriptionQuery query = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).dmpSubQuery(this.queryFactory.query(DmpQuery.class).isActive(IsActive.Active).statuses(DmpStatus.Finalized).accessTypes(DmpAccessType.Public)); + DescriptionQuery query = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).dmpSubQuery(this.queryFactory.query(DmpQuery.class).isActive(IsActive.Active).statuses(DmpStatus.Finalized).accessTypes(DmpAccessType.Public)); PublicDescription model = this.builderFactory.builder(PublicDescriptionBuilder.class).authorize(EnumSet.of(Public)).build(fieldSet, query.firstAs(fieldSet)); - if (model == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, PublicDescription.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (model == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, PublicDescription.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.auditService.track(AuditableAction.Description_PublicLookup, Map.ofEntries( new AbstractMap.SimpleEntry("id", id), @@ -160,18 +164,24 @@ public class DescriptionController { @PostMapping("query") @OperationWithTenantHeader(summary = "Query all descriptions", description = SwaggerHelpers.Description.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.Description.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.Description.endpoint_query_request_body_example - ) - } - )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(examples = @ExampleObject( - name = "First page", - description = "Example with the first page of paginated results", - value = SwaggerHelpers.Description.endpoint_query_response_example - )))) + examples = { + @ExampleObject( + name = "Pagination and projection", + description = "Simple paginated request using a property projection list and pagination info", + value = SwaggerHelpers.Description.endpoint_query_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = Description.class + ) + ), + examples = @ExampleObject( + name = "First page", + description = "Example with the first page of paginated results", + value = SwaggerHelpers.Description.endpoint_query_response_example + )))) public QueryResult query(@RequestBody DescriptionLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", Description.class.getSimpleName()); @@ -184,7 +194,6 @@ public class DescriptionController { return queryResult; } - @GetMapping("{id}") @OperationWithTenantHeader(summary = "Fetch a specific description by id") @Swagger404 @@ -222,7 +231,7 @@ public class DescriptionController { ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException { logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); - + Description persisted = this.descriptionService.persist(model, fieldSet); this.auditService.track(AuditableAction.Description_Persist, Map.ofEntries( @@ -245,7 +254,7 @@ public class DescriptionController { ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); - + Description persisted = this.descriptionService.persistStatus(model, fieldSet); this.auditService.track(AuditableAction.Description_PersistStatus, Map.ofEntries( @@ -327,7 +336,7 @@ public class DescriptionController { ) throws IOException { logger.debug(new MapLogEntry("uploadFieldFiles" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); - + StorageFile persisted = this.descriptionService.uploadFieldFile(model, file, fieldSet); this.auditService.track(AuditableAction.Description_UploadFieldFiles, Map.ofEntries( @@ -350,18 +359,20 @@ public class DescriptionController { StorageFileEntity storageFile = this.descriptionService.getFieldFile(id, fileId); byte[] file = this.storageFileService.readAsBytesSafe(id); - if (file == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (file == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.auditService.track(AuditableAction.Description_GetFieldFile, Map.ofEntries( new AbstractMap.SimpleEntry("id", id) )); String contentType = storageFile.getMimeType(); - if (this.conventionService.isNullOrEmpty(contentType)) contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; + if (this.conventionService.isNullOrEmpty(contentType)) + contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; return ResponseEntity.ok() .contentType(MediaType.valueOf(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + storageFile.getName() + (storageFile.getExtension().startsWith(".") ? "" : ".") + storageFile.getExtension() + "\"") + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + storageFile.getName() + (storageFile.getExtension().startsWith(".") ? "" : ".") + storageFile.getExtension() + "\"") .body(new ByteArrayResource(file)); } diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java index 692c7f6c1..591988ef4 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java @@ -13,9 +13,21 @@ 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.xml.bind.JAXBException; import org.opencdmp.audit.AuditableAction; import org.opencdmp.authorization.AuthorizationFlags; +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.DescriptionTemplateEntity; import org.opencdmp.model.builder.descriptiontemplate.DescriptionTemplateBuilder; import org.opencdmp.model.censorship.descriptiontemplate.DescriptionTemplateCensor; @@ -49,6 +61,8 @@ import java.util.UUID; @RestController @RequestMapping(path = "api/description-template") +@Tag(name = "Description Templates", description = "Manage description templates") +@SwaggerCommonErrorResponses public class DescriptionTemplateController { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DescriptionTemplateController.class)); @@ -64,25 +78,45 @@ public class DescriptionTemplateController { private final QueryFactory queryFactory; private final MessageSource messageSource; + private final FieldSetExpanderService fieldSetExpanderService; public DescriptionTemplateController( - BuilderFactory builderFactory, - AuditService auditService, - DescriptionTemplateService descriptionTemplateTypeService, - CensorFactory censorFactory, - QueryFactory queryFactory, - MessageSource messageSource, FieldSetExpanderService fieldSetExpanderService) { + BuilderFactory builderFactory, + AuditService auditService, + DescriptionTemplateService descriptionTemplateTypeService, + CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource, FieldSetExpanderService fieldSetExpanderService) { this.builderFactory = builderFactory; this.auditService = auditService; this.descriptionTemplateTypeService = descriptionTemplateTypeService; this.censorFactory = censorFactory; this.queryFactory = queryFactory; this.messageSource = messageSource; - this.fieldSetExpanderService = fieldSetExpanderService; + this.fieldSetExpanderService = fieldSetExpanderService; } @PostMapping("query") + @OperationWithTenantHeader(summary = "Query all description templates", description = SwaggerHelpers.DescriptionTemplate.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.DescriptionTemplate.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.DescriptionTemplate.endpoint_query_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = DescriptionTemplate.class + ) + ), + examples = @ExampleObject( + name = "First page", + description = "Example with the first page of paginated results", + value = SwaggerHelpers.DescriptionTemplate.endpoint_query_response_example + )))) public QueryResult query(@RequestBody DescriptionTemplateLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", DescriptionTemplate.class.getSimpleName()); @@ -100,7 +134,12 @@ public class DescriptionTemplateController { } @GetMapping("{id}") - public DescriptionTemplate get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + @OperationWithTenantHeader(summary = "Fetch a specific description template by id") + @Swagger404 + public DescriptionTemplate get( + @Parameter(name = "id", description = "The id of a description template 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" + DescriptionTemplate.class.getSimpleName()).And("id", id).And("fields", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); @@ -120,9 +159,15 @@ public class DescriptionTemplateController { } @PostMapping("persist") + @OperationWithTenantHeader(summary = "Create a new or update an existing description template") + @Swagger400 + @Swagger404 @Transactional @ValidationFilterAnnotation(validator = DescriptionTemplatePersist.DescriptionTemplatePersistValidator.ValidatorName, argumentName = "model") - public DescriptionTemplate persist(@RequestBody DescriptionTemplatePersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { + public DescriptionTemplate persist( + @RequestBody DescriptionTemplatePersist 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" + DescriptionTemplate.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); new BaseFieldSet(fieldSet.getFields()).ensure(DescriptionTemplate._id); @@ -137,8 +182,12 @@ public class DescriptionTemplateController { } @DeleteMapping("{id}") + @OperationWithTenantHeader(summary = "Delete a description template by id") + @Swagger404 @Transactional - public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + public void delete( + @Parameter(name = "id", description = "The id of a description template to delete", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id + ) throws MyForbiddenException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving" + DescriptionTemplate.class.getSimpleName()).And("id", id)); this.descriptionTemplateTypeService.deleteAndSave(id); @@ -147,7 +196,12 @@ public class DescriptionTemplateController { } @GetMapping("clone/{id}") - public DescriptionTemplate buildClone(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + @OperationWithTenantHeader(summary = "Clone a description template by id") + @Swagger404 + public DescriptionTemplate buildClone( + @Parameter(name = "id", description = "The id of a description template to clone", 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("clone" + DmpBlueprint.class.getSimpleName()).And("id", id).And("fields", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); @@ -164,9 +218,15 @@ public class DescriptionTemplateController { } @PostMapping("new-version") + @OperationWithTenantHeader(summary = "Create a new version of a description template") + @Swagger400 + @Swagger404 @Transactional @ValidationFilterAnnotation(validator = NewVersionDescriptionTemplatePersist.NewVersionDescriptionTemplatePersistValidator.ValidatorName, argumentName = "model") - public DescriptionTemplate createNewVersion(@RequestBody NewVersionDescriptionTemplatePersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { + public DescriptionTemplate createNewVersion( + @RequestBody NewVersionDescriptionTemplatePersist 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" + NewVersionDescriptionTemplatePersist.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); DescriptionTemplate persisted = this.descriptionTemplateTypeService.createNewVersion(model, fieldSet); @@ -180,7 +240,11 @@ public class DescriptionTemplateController { } @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") - public @ResponseBody ResponseEntity getXml(@PathVariable UUID id) throws JAXBException, ParserConfigurationException, IOException, TransformerException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException { + @OperationWithTenantHeader(summary = "Export a description template in xml by id") + @Swagger404 + public @ResponseBody ResponseEntity getXml( + @Parameter(name = "id", description = "The id of a description template to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id + ) throws JAXBException, ParserConfigurationException, IOException, TransformerException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException { logger.debug(new MapLogEntry("export" + DescriptionTemplate.class.getSimpleName()).And("id", id)); ResponseEntity response = this.descriptionTemplateTypeService.exportXml(id); @@ -193,8 +257,13 @@ public class DescriptionTemplateController { } @RequestMapping(method = RequestMethod.POST, value = {"/xml/import/{groupId}", "/xml/import"}) + @OperationWithTenantHeader(summary = "Import a description template from an xml file") @Transactional - public DescriptionTemplate importXml(@RequestParam("file") MultipartFile file, @PathVariable(value = "groupId", required = false) UUID groupId, FieldSet fieldSet) throws IOException, JAXBException, InvalidApplicationException, ParserConfigurationException, TransformerException, InstantiationException, IllegalAccessException, SAXException { + public DescriptionTemplate importXml( + @RequestParam("file") MultipartFile file, + @Parameter(name = "groupId", description = "The group id of a description template to import. This is optional.", example = "c0c163dc-2965-45a5-9608-f76030578609") @PathVariable(value = "groupId", required = false) UUID groupId, + @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet + ) throws IOException, JAXBException, InvalidApplicationException, ParserConfigurationException, TransformerException, InstantiationException, IllegalAccessException, SAXException { logger.debug(new MapLogEntry("import" + DescriptionTemplate.class.getSimpleName()).And("file", file).And("groupId", groupId)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java index 45e4741bd..49c404928 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java @@ -11,8 +11,20 @@ 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 org.opencdmp.audit.AuditableAction; import org.opencdmp.authorization.AuthorizationFlags; +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.DescriptionTemplateTypeEntity; import org.opencdmp.model.DescriptionTemplateType; import org.opencdmp.model.builder.DescriptionTemplateTypeBuilder; @@ -33,10 +45,14 @@ import java.util.*; @RestController @RequestMapping(path = "api/description-template-type") +@Tag(name = "Description Template Types", description = "Manage description template types") +@SwaggerCommonErrorResponses public class DescriptionTemplateTypeController { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DescriptionTemplateTypeController.class)); + private final BuilderFactory builderFactory; + private final AuditService auditService; private final DescriptionTemplateTypeService descriptionTemplateTypeService; @@ -63,6 +79,25 @@ public class DescriptionTemplateTypeController { } @PostMapping("query") + @OperationWithTenantHeader(summary = "Query all description template types", description = SwaggerHelpers.DescriptionTemplateType.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.DescriptionTemplateType.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.DescriptionTemplateType.endpoint_query_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = DescriptionTemplateType.class + ) + ), + examples = @ExampleObject( + name = "First page", + description = "Example with the first page of paginated results", + value = SwaggerHelpers.DescriptionTemplateType.endpoint_query_response_example + )))) public QueryResult Query(@RequestBody DescriptionTemplateTypeLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", DescriptionTemplateType.class.getSimpleName()); @@ -81,7 +116,12 @@ public class DescriptionTemplateTypeController { } @GetMapping("{id}") - public DescriptionTemplateType Get(@PathVariable("id") UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + @OperationWithTenantHeader(summary = "Fetch a specific description template type by id") + @Swagger404 + public DescriptionTemplateType Get( + @Parameter(name = "id", description = "The id of a description template type 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, Locale locale + ) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + DescriptionTemplateType.class.getSimpleName()).And("id", id).And("fields", fieldSet)); this.censorFactory.censor(DescriptionTemplateTypeCensor.class).censor(fieldSet, null); @@ -101,9 +141,15 @@ public class DescriptionTemplateTypeController { } @PostMapping("persist") + @OperationWithTenantHeader(summary = "Create a new or update an existing description template type") + @Swagger400 + @Swagger404 @Transactional - @ValidationFilterAnnotation(validator = DescriptionTemplateTypePersist.DescriptionTemplateTypePersistValidator.ValidatorName, argumentName ="model") - public DescriptionTemplateType Persist(@RequestBody DescriptionTemplateTypePersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { + @ValidationFilterAnnotation(validator = DescriptionTemplateTypePersist.DescriptionTemplateTypePersistValidator.ValidatorName, argumentName = "model") + public DescriptionTemplateType Persist( + @RequestBody DescriptionTemplateTypePersist model, + @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet + ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { logger.debug(new MapLogEntry("persisting" + DescriptionTemplateType.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); DescriptionTemplateType persisted = this.descriptionTemplateTypeService.persist(model, fieldSet); @@ -116,8 +162,12 @@ public class DescriptionTemplateTypeController { } @DeleteMapping("{id}") + @OperationWithTenantHeader(summary = "Delete a description template type by id") + @Swagger404 @Transactional - public void Delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + public void Delete( + @Parameter(name = "id", description = "The id of a description template type to delete", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id + ) throws MyForbiddenException, InvalidApplicationException { logger.debug(new MapLogEntry("retrieving" + DescriptionTemplateType.class.getSimpleName()).And("id", id)); this.descriptionTemplateTypeService.deleteAndSave(id); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java b/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java index cfd819a6a..ae1fef59a 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java @@ -11,9 +11,7 @@ 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.annotations.ApiImplicitParam; import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; @@ -149,11 +147,15 @@ public class DmpController { } @PostMapping("query") - @OperationWithTenantHeader(summary = "Query all plans", description = SwaggerHelpers.Dmp.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.Dmp.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.Dmp.endpoint_query_request_body_example - ))), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + @OperationWithTenantHeader(summary = "Query all plans", description = SwaggerHelpers.Dmp.endpoint_query, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = SwaggerHelpers.Dmp.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.Dmp.endpoint_query_request_body_example + ) + } + )), responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( array = @ArraySchema( schema = @Schema( implementation = Dmp.class diff --git a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java index 718532cfd..b08e7c428 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java @@ -903,11 +903,6 @@ public class SwaggerHelpers { "count":2912 } """; - - public static final String endpoint_ = - """ - - """; } public static class Description { @@ -1765,4 +1760,567 @@ public class SwaggerHelpers { """; } + public static class DescriptionTemplate { + + public static final String endpoint_query = + """ + This endpoint is used to fetch all the available description templates.
+ It also allows to restrict the results using a query object passed in the request body.
+ """; + + public static final String endpoint_query_request_body = + """ + Let's explore the options this object gives us. + + ### General query parameters: + +
    +
  • page: + This is an object controlling the pagination of the results. It contains two properties. +
  • +
      +
    • offset: + How many records to omit. +
    • +
    • size: + How many records to include in each page. +
    • +
    +
+ + For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object: + + ```JSON + { + "offset": 30, + "size": 15 + } + ``` + +
    +
  • order: + This is an object controlling the ordering of the results. + It contains a list of strings called items with the names of the properties to use. +
    If the name of the property is prefixed with a '-', the ordering direction is DESC. Otherwise, it is ASC. +
  • +
+ + For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object: + + ```JSON + { + "items": [ + "-createdAt" + ], + } + ``` + +
    +
  • metadata: + This is an object containing metadata for the request. There is only one available option. +
      +
    • countAll: + If this is set to true, the count property included in the response will account for all the records regardless the pagination, + with all the rest of filtering options applied of course. + Otherwise, if it is set to false or not present, only the returned results will be counted. +
      The first option is useful for the UI clients to calculate how many result pages are available. +
    • +
    +
  • +
  • project: + This is an object controlling the data projection of the results. + It contains a list of strings called fields with the names of the properties to project. +
    You can also include properties that are deeper in the object tree by prefixing them with dots. +
  • +
+ + ### Description template specific query parameters: + +
    +
  • like: + If there is a like parameter present in the query, only the description template entities that include the contents of the parameter either in their labels or the descriptions will be in the response. +
  • +
  • ids: + This is a list and contains the ids we want to include in the response.
    If empty, every record is included. +
  • +
  • excludedIds: + This is a list and contains the ids we want to exclude from the response.
    If empty, no record gets excluded. +
  • +
  • groupIds: + This is a list and contains the group ids we want the templates to have. Every template and all its versions, have the same groupId.
    If empty, every record is included. +
  • +
  • excludedGroupIds: + This is a list and contains the group ids we want the templates not to have. Every template and all its versions, have the same groupId.
    If empty, no record gets excluded. +
  • +
  • typeIds: + This is a list and contains the type ids we want the templates to have. Every template has a type designated by a type id.
    If empty, every record is included. +
  • +
  • isActive: + 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]. +
    If not present or if we pass [0,1], every record is included. +
  • +
  • statuses: + This is a list and determines which records we want to include in the response, based on their status. + The status can be Draft or Finalized. We add 0 or 1 to the list respectively. +
    If not present, every record is included. +
  • +
  • versionStatuses: + This is a list and determines which records we want to include in the response, based on their version status. + The status can be Current, Previous or NotFinalized. We add 0, 1 or 2 to the list respectively. +
    If not present, every record is included. +
  • +
  • onlyCanEdit: + This is a boolean and determines whether to fetch only the templates the user can edit or every one. +
    If not present, every record is included. +
  • +
+ """; + + public static final String endpoint_query_request_body_example = + """ + { + "project":{ + "fields":[ + "id", + "label", + "description", + "status", + "version", + "groupId", + "updatedAt", + "createdAt", + "hash", + "belongsToCurrentTenant", + "isActive", + "authorizationFlags.EditDescriptionTemplate", + "authorizationFlags.DeleteDescriptionTemplate", + "authorizationFlags.CloneDescriptionTemplate", + "authorizationFlags.CreateNewVersionDescriptionTemplate", + "authorizationFlags.ImportDescriptionTemplate", + "authorizationFlags.ExportDescriptionTemplate" + ] + }, + "metadata":{ + "countAll":true + }, + "page":{ + "offset":0, + "size":10 + }, + "isActive":[ + 1 + ], + "order":{ + "items":[ + "-createdAt" + ] + }, + "versionStatuses":[ + 0, + 2 + ], + "onlyCanEdit":true + } + """; + + public static final String endpoint_query_response_example = + """ + { + "items":[ + { + "id":"fbbea99b-9259-430b-9f1b-c78f3df8a07c", + "label":"url", + "description":"zxccx", + "groupId":"71d1b878-9980-4b35-b58d-98597e1f2579", + "version":1, + "createdAt":"2024-06-21T13:13:38.344835Z", + "updatedAt":"2024-06-21T13:13:43.001378Z", + "isActive":1, + "status":1, + "hash":"1718975623", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"6d2c5983-6125-4fc0-833a-3618d35d0c72", + "label":"to-delete", + "description":"test", + "groupId":"95fc6c79-ee17-4cd9-bad7-0d70ce17b9a4", + "version":1, + "createdAt":"2024-06-21T10:22:33.116729Z", + "updatedAt":"2024-06-21T10:22:52.668175Z", + "isActive":1, + "status":0, + "hash":"1718965372", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"e0cb75fe-6f3b-4770-8857-f6b3de9d5411", + "label":"Test Purple Roles", + "description":"Test Purple Roles", + "groupId":"bd3159c7-0b42-431c-8c0f-b353c1343b79", + "version":1, + "createdAt":"2024-06-19T09:53:10.567662Z", + "updatedAt":"2024-06-19T09:53:18.122353Z", + "isActive":1, + "status":1, + "hash":"1718790798", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"17eeb5ef-0f2a-40bb-b9b3-4e89a749a08a", + "label":"Purple_template_1.xml", + "description":"Desc", + "groupId":"419bbe42-91e0-4ba4-97cf-c9502063a7ef", + "version":1, + "createdAt":"2024-06-19T09:30:34.925496Z", + "updatedAt":"2024-06-19T09:31:05.429197Z", + "isActive":1, + "status":1, + "hash":"1718789465", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"9f6bd3bc-b611-4641-bceb-2e64b0bd34cd", + "label":"RDA_test_new_.xml", + "description":"RDA test", + "groupId":"3c88259f-4b16-4fc3-adf2-cee2ea4b882d", + "version":2, + "createdAt":"2024-06-19T07:14:11.748649Z", + "updatedAt":"2024-06-19T07:14:11.748649Z", + "isActive":1, + "status":0, + "hash":"1718781251", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"212342fe-ab6f-4604-b80e-ac23aca93c76", + "label":"RDA test with fix multiplicity1", + "description":"RDA test", + "groupId":"4ff3e924-8623-4b58-a61b-94740b67c4aa", + "version":1, + "createdAt":"2024-06-18T14:04:55.574099Z", + "updatedAt":"2024-06-20T10:45:35.188002Z", + "isActive":1, + "status":1, + "hash":"1718880335", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"dddf404e-da59-4097-86d9-29f085ee40da", + "label":"RDA test new ", + "description":"RDA test", + "groupId":"3c88259f-4b16-4fc3-adf2-cee2ea4b882d", + "version":1, + "createdAt":"2024-06-17T12:09:24.296094Z", + "updatedAt":"2024-06-17T12:20:40.432586Z", + "isActive":1, + "status":1, + "hash":"1718626840", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"fa67ecbf-c3b4-4e19-b531-764cf7b4d508", + "label":"test", + "description":"test", + "groupId":"bd1a655e-a47b-4edc-a9a2-214dc2971c12", + "version":1, + "createdAt":"2024-06-17T08:41:36.664047Z", + "updatedAt":"2024-06-17T08:41:36.678049Z", + "isActive":1, + "status":0, + "hash":"1718613696", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"54527748-7c8a-4045-8b8e-f7be65900302", + "label":"RDA test", + "description":"RDA test", + "groupId":"bf36f613-422a-4d4a-aee1-8a377157f265", + "version":1, + "createdAt":"2024-06-14T08:10:55.988008Z", + "updatedAt":"2024-06-14T10:42:17.438789Z", + "isActive":1, + "status":1, + "hash":"1718361737", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + }, + { + "id":"d2f41a59-d369-431d-bf3a-eb29fb4423e7", + "label":"CHIST-ERA Data Management", + "description":"This is a DMP template created based on the CHIST-ERA RDM policy and DMP template.", + "groupId":"c8ef1ecc-f0a6-4f06-a62d-2769968c3d0a", + "version":3, + "createdAt":"2024-04-17T16:58:57Z", + "updatedAt":"2024-04-17T16:58:57Z", + "isActive":1, + "status":1, + "hash":"1713373137", + "authorizationFlags":[ + "CreateNewVersionDescriptionTemplate", + "CloneDescriptionTemplate", + "ImportDescriptionTemplate", + "DeleteDescriptionTemplate", + "EditDescriptionTemplate", + "ExportDescriptionTemplate" + ], + "belongsToCurrentTenant":true + } + ], + "count":40 + } + """; + } + + public static class DescriptionTemplateType { + + public static final String endpoint_query = + """ + This endpoint is used to fetch all the available description template types.
+ It also allows to restrict the results using a query object passed in the request body.
+ """; + + public static final String endpoint_query_request_body = + """ + Let's explore the options this object gives us. + + ### General query parameters: + +
    +
  • page: + This is an object controlling the pagination of the results. It contains two properties. +
  • +
      +
    • offset: + How many records to omit. +
    • +
    • size: + How many records to include in each page. +
    • +
    +
+ + For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object: + + ```JSON + { + "offset": 30, + "size": 15 + } + ``` + +
    +
  • order: + This is an object controlling the ordering of the results. + It contains a list of strings called items with the names of the properties to use. +
    If the name of the property is prefixed with a '-', the ordering direction is DESC. Otherwise, it is ASC. +
  • +
+ + For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object: + + ```JSON + { + "items": [ + "-createdAt" + ], + } + ``` + +
    +
  • metadata: + This is an object containing metadata for the request. There is only one available option. +
      +
    • countAll: + If this is set to true, the count property included in the response will account for all the records regardless the pagination, + with all the rest of filtering options applied of course. + Otherwise, if it is set to false or not present, only the returned results will be counted. +
      The first option is useful for the UI clients to calculate how many result pages are available. +
    • +
    +
  • +
  • project: + This is an object controlling the data projection of the results. + It contains a list of strings called fields with the names of the properties to project. +
    You can also include properties that are deeper in the object tree by prefixing them with dots. +
  • +
+ + ### Description template type specific query parameters: + +
    +
  • like: + If there is a like parameter present in the query, only the description template type entities that include the contents of the parameter in their labels will be in the response. +
  • +
  • ids: + This is a list and contains the ids we want to include in the response.
    If empty, every record is included. +
  • +
  • excludedIds: + This is a list and contains the ids we want to exclude from the response.
    If empty, no record gets excluded. +
  • +
  • isActives: + 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]. +
    If not present or if we pass [0,1], every record is included. +
  • +
  • statuses: + This is a list and determines which records we want to include in the response, based on their status. + The status can be Draft or Finalized. We add 0 or 1 to the list respectively. +
    If not present, every record is included. +
  • +
+ """; + + public static final String endpoint_query_request_body_example = + """ + { + "project":{ + "fields":[ + "id", + "name", + "status", + "updatedAt", + "createdAt", + "hash", + "belongsToCurrentTenant", + "isActive" + ] + }, + "metadata":{ + "countAll":true + }, + "page":{ + "offset":0, + "size":10 + }, + "isActive":[ + 1 + ], + "order":{ + "items":[ + "-createdAt" + ] + } + } + """; + + public static final String endpoint_query_response_example = + """ + { + "items":[ + { + "id":"709a8400-10ca-11ee-be56-0242ac120002", + "name":"Dataset", + "createdAt":"2024-06-10T07:54:59.508261Z", + "updatedAt":"2024-06-10T07:54:59.508261Z", + "isActive":1, + "status":1, + "hash":"1718006099", + "belongsToCurrentTenant":true + }, + { + "id":"3b15c046-a978-4b5a-9376-66525b7f1ac9", + "name":"Software", + "createdAt":"2024-06-10T07:54:59.508261Z", + "updatedAt":"2024-06-10T07:54:59.508261Z", + "isActive":1, + "status":1, + "hash":"1718006099", + "belongsToCurrentTenant":true + } + ], + "count":2 + } + """; + + } + + public static class DmpBlueprint { + + } + + public static class FileTransformer { + + } + + public static class EntityDoi { + + } + + public static class Deposit { + + } + } diff --git a/backend/web/src/main/resources/config/swagger.yml b/backend/web/src/main/resources/config/swagger.yml index 6bd94de52..ac2bd06c4 100644 --- a/backend/web/src/main/resources/config/swagger.yml +++ b/backend/web/src/main/resources/config/swagger.yml @@ -3,15 +3,15 @@ springdoc: groups: enabled: true groupConfigs: - - group: public-api + - group: legacy-api displayName: Legacy packagesToScan: org.opencdmp.controllers.publicapi pathsToMatch: "/api/public/dmps/**, /api/public/datasets/**" - - group: internal-api + - group: current-api displayName: Current packagesToScan: org.opencdmp.controllers packagesToExclude: org.opencdmp.controllers.publicapi - pathsToMatch: "/api/dmp/**, /api/description/**" + pathsToMatch: "/api/dmp/**, /api/description/**, /api/description-template/**, /api/description-template-type/**" swaggerUi: enabled: true useRootPath: true