diff --git a/apps/openchallenges/api-gateway/src/main/java/org/sagebionetworks/openchallenges/api/gateway/config/SecurityConfig.java b/apps/openchallenges/api-gateway/src/main/java/org/sagebionetworks/openchallenges/api/gateway/config/SecurityConfig.java index faf8cb9c97..fcff064fdc 100644 --- a/apps/openchallenges/api-gateway/src/main/java/org/sagebionetworks/openchallenges/api/gateway/config/SecurityConfig.java +++ b/apps/openchallenges/api-gateway/src/main/java/org/sagebionetworks/openchallenges/api/gateway/config/SecurityConfig.java @@ -19,6 +19,7 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { .permitAll().pathMatchers("/api/v1/challengeInputDataTypes/**") .permitAll().pathMatchers("/api/v1/challengePlatforms/**") .permitAll().pathMatchers("/api/v1/challenges/**") + .permitAll().pathMatchers("/api/v1/edamConcepts/**") .permitAll().pathMatchers("/api/v1/images/**") .permitAll().pathMatchers("/api/v1/organizations/**") .permitAll() diff --git a/apps/openchallenges/challenge-service/.openapi-generator/FILES b/apps/openchallenges/challenge-service/.openapi-generator/FILES index c61f3338b6..382720e17a 100644 --- a/apps/openchallenges/challenge-service/.openapi-generator/FILES +++ b/apps/openchallenges/challenge-service/.openapi-generator/FILES @@ -13,6 +13,9 @@ src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/Challenge src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/ChallengePlatformApi.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/ChallengePlatformApiController.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/ChallengePlatformApiDelegate.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApi.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiController.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegate.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/configuration/EnumConverterConfiguration.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/configuration/HomeController.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/configuration/SpringDocConfiguration.java @@ -35,6 +38,9 @@ src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/Cha src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/ChallengeSubmissionTypeDto.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/ChallengesPageDto.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/ChallengesPerYearDto.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptDto.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptSearchQueryDto.java +src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptsPageDto.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamDataDto.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamOperationDto.java src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/PageMetadataDto.java diff --git a/apps/openchallenges/challenge-service/build.gradle b/apps/openchallenges/challenge-service/build.gradle index 4eecf5c2ec..a0bc114ca7 100644 --- a/apps/openchallenges/challenge-service/build.gradle +++ b/apps/openchallenges/challenge-service/build.gradle @@ -100,13 +100,43 @@ jacocoTestReport { reports { xml.required = true } + + afterEvaluate { + // A single star (*) selects files AND subdirectories. + // Exclude Java classes from the JaCoCo report. These files must be excluded from SonarCloud + // report separatedly (see SonarCloud config) + def autoGeneratedFiles = [ + '**/api/*.*', + '**/configuration/EnumConverterConfiguration*.*', + '**/configuration/HomeController*.*', + '**/configuration/SpringDocConfiguration*.*', + '**/model/dto/**', + '**/RFC3339DateFormat.*' + ] + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: autoGeneratedFiles + ) + })) + } } sonar { + // A single star (*) only selects files, not subdirectories. + def autoGeneratedFiles = [ + '**/api/*', + '**/configuration/EnumConverterConfiguration*.*', + '**/configuration/HomeController*.*', + '**/configuration/SpringDocConfiguration*.*', + '**/model/dto/**', + '**/RFC3339DateFormat.*' + ] properties { property("sonar.host.url", "https://sonarcloud.io") property("sonar.organization", "sage-bionetworks") property("sonar.projectKey", "${project.name}") + // We still scan the auto-generated files to be aware of the bugs and code smells they include. + // property("sonar.exclusions", autoGeneratedFiles.join(",")) + property("sonar.coverage.exclusions", autoGeneratedFiles.join(",")) // Include the pull request number (ignored if not set) property("sonar.pullrequest.key", System.getenv('SONAR_PULL_REQUEST_NUMBER')) } diff --git a/apps/openchallenges/challenge-service/requests.http b/apps/openchallenges/challenge-service/requests.http index 2d18b520cf..35a5bc0b61 100644 --- a/apps/openchallenges/challenge-service/requests.http +++ b/apps/openchallenges/challenge-service/requests.http @@ -134,4 +134,12 @@ GET {{basePath}}/challenges/1/contributions ### Get the number of challenges tracked per year. -GET {{basePath}}/challengeAnalytics/challengesPerYear \ No newline at end of file +GET {{basePath}}/challengeAnalytics/challengesPerYear + +### List the EDAM concepts + +GET {{basePath}}/edamConcepts + +### List the EDAM concepts that match the space-separated search terms "sequence". + +GET {{basePath}}/edamConcepts?searchTerms=sequence \ No newline at end of file diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApi.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApi.java new file mode 100644 index 0000000000..3be8d52e5d --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApi.java @@ -0,0 +1,90 @@ +/** + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (6.2.1). + * https://openapi-generator.tech Do not edit the class manually. + */ +package org.sagebionetworks.openchallenges.challenge.service.api; + +import io.swagger.v3.oas.annotations.Operation; +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 javax.annotation.Generated; +import javax.validation.Valid; +import javax.validation.constraints.*; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.BasicErrorDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptsPageDto; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +@Validated +@Tag(name = "EdamConcept", description = "Operations about EDAM concepts.") +public interface EdamConceptApi { + + default EdamConceptApiDelegate getDelegate() { + return new EdamConceptApiDelegate() {}; + } + + /** + * GET /edamConcepts : List EDAM concepts List EDAM concepts + * + * @param edamConceptSearchQuery The search query used to find EDAM concepts. (optional) + * @return Success (status code 200) or Invalid request (status code 400) or The request cannot be + * fulfilled due to an unexpected server error (status code 500) + */ + @Operation( + operationId = "listEdamConcepts", + summary = "List EDAM concepts", + tags = {"EdamConcept"}, + responses = { + @ApiResponse( + responseCode = "200", + description = "Success", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = EdamConceptsPageDto.class)), + @Content( + mediaType = "application/problem+json", + schema = @Schema(implementation = EdamConceptsPageDto.class)) + }), + @ApiResponse( + responseCode = "400", + description = "Invalid request", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = BasicErrorDto.class)), + @Content( + mediaType = "application/problem+json", + schema = @Schema(implementation = BasicErrorDto.class)) + }), + @ApiResponse( + responseCode = "500", + description = "The request cannot be fulfilled due to an unexpected server error", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = BasicErrorDto.class)), + @Content( + mediaType = "application/problem+json", + schema = @Schema(implementation = BasicErrorDto.class)) + }) + }) + @RequestMapping( + method = RequestMethod.GET, + value = "/edamConcepts", + produces = {"application/json", "application/problem+json"}) + default ResponseEntity listEdamConcepts( + @Parameter( + name = "edamConceptSearchQuery", + description = "The search query used to find EDAM concepts.") + @Valid + EdamConceptSearchQueryDto edamConceptSearchQuery) { + return getDelegate().listEdamConcepts(edamConceptSearchQuery); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiController.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiController.java new file mode 100644 index 0000000000..86ae5025e1 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiController.java @@ -0,0 +1,25 @@ +package org.sagebionetworks.openchallenges.challenge.service.api; + +import java.util.Optional; +import javax.annotation.Generated; +import javax.validation.constraints.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +@Controller +@RequestMapping("${openapi.openChallengesChallengeREST.base-path:/v1}") +public class EdamConceptApiController implements EdamConceptApi { + + private final EdamConceptApiDelegate delegate; + + public EdamConceptApiController(@Autowired(required = false) EdamConceptApiDelegate delegate) { + this.delegate = Optional.ofNullable(delegate).orElse(new EdamConceptApiDelegate() {}); + } + + @Override + public EdamConceptApiDelegate getDelegate() { + return delegate; + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegate.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegate.java new file mode 100644 index 0000000000..06b9673a7a --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegate.java @@ -0,0 +1,52 @@ +package org.sagebionetworks.openchallenges.challenge.service.api; + +import java.util.Optional; +import javax.annotation.Generated; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptsPageDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.NativeWebRequest; + +/** + * A delegate to be called by the {@link EdamConceptApiController}}. Implement this interface with a + * {@link org.springframework.stereotype.Service} annotated class. + */ +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +public interface EdamConceptApiDelegate { + + default Optional getRequest() { + return Optional.empty(); + } + + /** + * GET /edamConcepts : List EDAM concepts List EDAM concepts + * + * @param edamConceptSearchQuery The search query used to find EDAM concepts. (optional) + * @return Success (status code 200) or Invalid request (status code 400) or The request cannot be + * fulfilled due to an unexpected server error (status code 500) + * @see EdamConceptApi#listEdamConcepts + */ + default ResponseEntity listEdamConcepts( + EdamConceptSearchQueryDto edamConceptSearchQuery) { + getRequest() + .ifPresent( + request -> { + for (MediaType mediaType : MediaType.parseMediaTypes(request.getHeader("Accept"))) { + if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { + String exampleString = "null"; + ApiUtil.setExampleResponse(request, "application/json", exampleString); + break; + } + if (mediaType.isCompatibleWith(MediaType.valueOf("application/problem+json"))) { + String exampleString = + "Custom MIME type example not yet supported: application/problem+json"; + ApiUtil.setExampleResponse(request, "application/problem+json", exampleString); + break; + } + } + }); + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegateImpl.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegateImpl.java new file mode 100644 index 0000000000..94f741668c --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/api/EdamConceptApiDelegateImpl.java @@ -0,0 +1,22 @@ +package org.sagebionetworks.openchallenges.challenge.service.api; + +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptsPageDto; +import org.sagebionetworks.openchallenges.challenge.service.service.EdamConceptService; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class EdamConceptApiDelegateImpl implements EdamConceptApiDelegate { + + private final EdamConceptService edamConceptService; + + public EdamConceptApiDelegateImpl(EdamConceptService edamConceptService) { + this.edamConceptService = edamConceptService; + } + + @Override + public ResponseEntity listEdamConcepts(EdamConceptSearchQueryDto query) { + return ResponseEntity.ok(edamConceptService.listEdamConcepts(query)); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptDto.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptDto.java new file mode 100644 index 0000000000..28d448c167 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptDto.java @@ -0,0 +1,132 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.*; +import java.util.Objects; +import javax.annotation.Generated; +import javax.validation.constraints.*; + +/** The EDAM concept. */ +@Schema(name = "EdamConcept", description = "The EDAM concept.") +@JsonTypeName("EdamConcept") +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +// TODO Add x-java-class-annotations +public class EdamConceptDto { + + @JsonProperty("id") + private Long id; + + @JsonProperty("classId") + private String classId; + + @JsonProperty("preferredLabel") + private String preferredLabel; + + public EdamConceptDto id(Long id) { + this.id = id; + return this; + } + + /** + * The unique identifier of the EDAM concept. + * + * @return id + */ + @NotNull + @Schema( + name = "id", + example = "1", + description = "The unique identifier of the EDAM concept.", + required = true) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public EdamConceptDto classId(String classId) { + this.classId = classId; + return this; + } + + /** + * Get classId + * + * @return classId + */ + @NotNull + @Size(max = 60) + @Schema(name = "classId", example = "http://edamontology.org/data_0850", required = true) + public String getClassId() { + return classId; + } + + public void setClassId(String classId) { + this.classId = classId; + } + + public EdamConceptDto preferredLabel(String preferredLabel) { + this.preferredLabel = preferredLabel; + return this; + } + + /** + * Get preferredLabel + * + * @return preferredLabel + */ + @NotNull + @Size(max = 80) + @Schema(name = "preferredLabel", example = "Sequence set", required = true) + public String getPreferredLabel() { + return preferredLabel; + } + + public void setPreferredLabel(String preferredLabel) { + this.preferredLabel = preferredLabel; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EdamConceptDto edamConcept = (EdamConceptDto) o; + return Objects.equals(this.id, edamConcept.id) + && Objects.equals(this.classId, edamConcept.classId) + && Objects.equals(this.preferredLabel, edamConcept.preferredLabel); + } + + @Override + public int hashCode() { + return Objects.hash(id, classId, preferredLabel); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class EdamConceptDto {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" classId: ").append(toIndentedString(classId)).append("\n"); + sb.append(" preferredLabel: ").append(toIndentedString(preferredLabel)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptSearchQueryDto.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptSearchQueryDto.java new file mode 100644 index 0000000000..7f60a7c236 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptSearchQueryDto.java @@ -0,0 +1,132 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.*; +import java.util.Objects; +import javax.annotation.Generated; +import javax.validation.constraints.*; + +/** An EDAM concept search query. */ +@Schema(name = "EdamConceptSearchQuery", description = "An EDAM concept search query.") +@JsonTypeName("EdamConceptSearchQuery") +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +// TODO Add x-java-class-annotations +public class EdamConceptSearchQueryDto { + + @JsonProperty("pageNumber") + private Integer pageNumber = 0; + + @JsonProperty("pageSize") + private Integer pageSize = 100; + + @JsonProperty("searchTerms") + private String searchTerms; + + public EdamConceptSearchQueryDto pageNumber(Integer pageNumber) { + this.pageNumber = pageNumber; + return this; + } + + /** + * The page number. minimum: 0 + * + * @return pageNumber + */ + @Min(0) + @Schema(name = "pageNumber", description = "The page number.", required = false) + public Integer getPageNumber() { + return pageNumber; + } + + public void setPageNumber(Integer pageNumber) { + this.pageNumber = pageNumber; + } + + public EdamConceptSearchQueryDto pageSize(Integer pageSize) { + this.pageSize = pageSize; + return this; + } + + /** + * The number of items in a single page. minimum: 1 + * + * @return pageSize + */ + @Min(1) + @Schema( + name = "pageSize", + description = "The number of items in a single page.", + required = false) + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public EdamConceptSearchQueryDto searchTerms(String searchTerms) { + this.searchTerms = searchTerms; + return this; + } + + /** + * A string of search terms used to filter the results. + * + * @return searchTerms + */ + @Schema( + name = "searchTerms", + example = "sequence image", + description = "A string of search terms used to filter the results.", + required = false) + public String getSearchTerms() { + return searchTerms; + } + + public void setSearchTerms(String searchTerms) { + this.searchTerms = searchTerms; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EdamConceptSearchQueryDto edamConceptSearchQuery = (EdamConceptSearchQueryDto) o; + return Objects.equals(this.pageNumber, edamConceptSearchQuery.pageNumber) + && Objects.equals(this.pageSize, edamConceptSearchQuery.pageSize) + && Objects.equals(this.searchTerms, edamConceptSearchQuery.searchTerms); + } + + @Override + public int hashCode() { + return Objects.hash(pageNumber, pageSize, searchTerms); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class EdamConceptSearchQueryDto {\n"); + sb.append(" pageNumber: ").append(toIndentedString(pageNumber)).append("\n"); + sb.append(" pageSize: ").append(toIndentedString(pageSize)).append("\n"); + sb.append(" searchTerms: ").append(toIndentedString(searchTerms)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptsPageDto.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptsPageDto.java new file mode 100644 index 0000000000..e684ec419e --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptsPageDto.java @@ -0,0 +1,260 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.annotation.Generated; +import javax.validation.Valid; +import javax.validation.constraints.*; + +/** A page of EDAM concepts. */ +@Schema(name = "EdamConceptsPage", description = "A page of EDAM concepts.") +@JsonTypeName("EdamConceptsPage") +@Generated(value = "org.openapitools.codegen.languages.SpringCodegen") +@lombok.Builder +public class EdamConceptsPageDto { + + @JsonProperty("number") + private Integer number; + + @JsonProperty("size") + private Integer size; + + @JsonProperty("totalElements") + private Long totalElements; + + @JsonProperty("totalPages") + private Integer totalPages; + + @JsonProperty("hasNext") + private Boolean hasNext; + + @JsonProperty("hasPrevious") + private Boolean hasPrevious; + + @JsonProperty("edamConcepts") + @Valid + private List edamConcepts = new ArrayList<>(); + + public EdamConceptsPageDto number(Integer number) { + this.number = number; + return this; + } + + /** + * The page number. + * + * @return number + */ + @NotNull + @Schema(name = "number", example = "99", description = "The page number.", required = true) + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public EdamConceptsPageDto size(Integer size) { + this.size = size; + return this; + } + + /** + * The number of items in a single page. + * + * @return size + */ + @NotNull + @Schema( + name = "size", + example = "99", + description = "The number of items in a single page.", + required = true) + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public EdamConceptsPageDto totalElements(Long totalElements) { + this.totalElements = totalElements; + return this; + } + + /** + * Total number of elements in the result set. + * + * @return totalElements + */ + @NotNull + @Schema( + name = "totalElements", + example = "99", + description = "Total number of elements in the result set.", + required = true) + public Long getTotalElements() { + return totalElements; + } + + public void setTotalElements(Long totalElements) { + this.totalElements = totalElements; + } + + public EdamConceptsPageDto totalPages(Integer totalPages) { + this.totalPages = totalPages; + return this; + } + + /** + * Total number of pages in the result set. + * + * @return totalPages + */ + @NotNull + @Schema( + name = "totalPages", + example = "99", + description = "Total number of pages in the result set.", + required = true) + public Integer getTotalPages() { + return totalPages; + } + + public void setTotalPages(Integer totalPages) { + this.totalPages = totalPages; + } + + public EdamConceptsPageDto hasNext(Boolean hasNext) { + this.hasNext = hasNext; + return this; + } + + /** + * Returns if there is a next page. + * + * @return hasNext + */ + @NotNull + @Schema( + name = "hasNext", + example = "true", + description = "Returns if there is a next page.", + required = true) + public Boolean getHasNext() { + return hasNext; + } + + public void setHasNext(Boolean hasNext) { + this.hasNext = hasNext; + } + + public EdamConceptsPageDto hasPrevious(Boolean hasPrevious) { + this.hasPrevious = hasPrevious; + return this; + } + + /** + * Returns if there is a previous page. + * + * @return hasPrevious + */ + @NotNull + @Schema( + name = "hasPrevious", + example = "true", + description = "Returns if there is a previous page.", + required = true) + public Boolean getHasPrevious() { + return hasPrevious; + } + + public void setHasPrevious(Boolean hasPrevious) { + this.hasPrevious = hasPrevious; + } + + public EdamConceptsPageDto edamConcepts(List edamConcepts) { + this.edamConcepts = edamConcepts; + return this; + } + + public EdamConceptsPageDto addEdamConceptsItem(EdamConceptDto edamConceptsItem) { + if (this.edamConcepts == null) { + this.edamConcepts = new ArrayList<>(); + } + this.edamConcepts.add(edamConceptsItem); + return this; + } + + /** + * A list of EDAM concepts. + * + * @return edamConcepts + */ + @NotNull + @Valid + @Schema(name = "edamConcepts", description = "A list of EDAM concepts.", required = true) + public List getEdamConcepts() { + return edamConcepts; + } + + public void setEdamConcepts(List edamConcepts) { + this.edamConcepts = edamConcepts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EdamConceptsPageDto edamConceptsPage = (EdamConceptsPageDto) o; + return Objects.equals(this.number, edamConceptsPage.number) + && Objects.equals(this.size, edamConceptsPage.size) + && Objects.equals(this.totalElements, edamConceptsPage.totalElements) + && Objects.equals(this.totalPages, edamConceptsPage.totalPages) + && Objects.equals(this.hasNext, edamConceptsPage.hasNext) + && Objects.equals(this.hasPrevious, edamConceptsPage.hasPrevious) + && Objects.equals(this.edamConcepts, edamConceptsPage.edamConcepts); + } + + @Override + public int hashCode() { + return Objects.hash( + number, size, totalElements, totalPages, hasNext, hasPrevious, edamConcepts); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class EdamConceptsPageDto {\n"); + sb.append(" number: ").append(toIndentedString(number)).append("\n"); + sb.append(" size: ").append(toIndentedString(size)).append("\n"); + sb.append(" totalElements: ").append(toIndentedString(totalElements)).append("\n"); + sb.append(" totalPages: ").append(toIndentedString(totalPages)).append("\n"); + sb.append(" hasNext: ").append(toIndentedString(hasNext)).append("\n"); + sb.append(" hasPrevious: ").append(toIndentedString(hasPrevious)).append("\n"); + sb.append(" edamConcepts: ").append(toIndentedString(edamConcepts)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/entity/EdamConceptEntity.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/entity/EdamConceptEntity.java new file mode 100644 index 0000000000..cc6b463eda --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/entity/EdamConceptEntity.java @@ -0,0 +1,37 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; + +@Entity +@Table(name = "edam_concept") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Indexed(index = "openchallenges-edam-concept") +public class EdamConceptEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + private Long id; + + @Column(name = "class_id", nullable = false) + @FullTextField(name = "class_id") + private String classId; + + @Column(name = "preferred_label", nullable = false) + @FullTextField(name = "preferred_label") + private String preferredLabel; +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/mapper/EdamConceptMapper.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/mapper/EdamConceptMapper.java new file mode 100644 index 0000000000..26a173d616 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/mapper/EdamConceptMapper.java @@ -0,0 +1,26 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.mapper; + +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptDto; +import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity; +import org.sagebionetworks.util.model.mapper.BaseMapper; +import org.springframework.beans.BeanUtils; + +public class EdamConceptMapper extends BaseMapper { + @Override + public EdamConceptEntity convertToEntity(EdamConceptDto dto, Object... args) { + EdamConceptEntity entity = new EdamConceptEntity(); + if (dto != null) { + BeanUtils.copyProperties(dto, entity); + } + return entity; + } + + @Override + public EdamConceptDto convertToDto(EdamConceptEntity entity, Object... args) { + EdamConceptDto dto = new EdamConceptDto(); + if (entity != null) { + BeanUtils.copyProperties(entity, dto); + } + return dto; + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepository.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepository.java new file mode 100644 index 0000000000..c188c0ec27 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepository.java @@ -0,0 +1,12 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.repository; + +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CustomEdamConceptRepository { + + Page findAll( + Pageable pageable, EdamConceptSearchQueryDto query, String... fields); +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepositoryImpl.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepositoryImpl.java new file mode 100644 index 0000000000..55207cbe23 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/CustomEdamConceptRepositoryImpl.java @@ -0,0 +1,85 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.repository; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.hibernate.search.engine.search.common.BooleanOperator; +import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; +import org.hibernate.search.engine.search.query.SearchResult; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +public class CustomEdamConceptRepositoryImpl implements CustomEdamConceptRepository { + + @PersistenceContext private EntityManager entityManager; + + @Override + public Page findAll( + Pageable pageable, EdamConceptSearchQueryDto query, String... fields) { + SearchResult result = getSearchResult(pageable, query, fields); + return new PageImpl<>(result.hits(), pageable, result.total().hitCount()); + } + + private SearchResult getSearchResult( + Pageable pageable, EdamConceptSearchQueryDto query, String[] fields) { + SearchSession searchSession = Search.session(entityManager); + SearchPredicateFactory pf = searchSession.scope(EdamConceptEntity.class).predicate(); + List predicates = new ArrayList<>(); + + if (query.getSearchTerms() != null && !query.getSearchTerms().isBlank()) { + predicates.add(getSearchTermsPredicate(pf, query, fields)); + } + + SearchPredicate topLevelPredicate = buildTopLevelPredicate(pf, predicates); + + return searchSession + .search(EdamConceptEntity.class) + .where(topLevelPredicate) + .fetch((int) pageable.getOffset(), pageable.getPageSize()); + } + + /** + * Searches the EDAM concepts using the search terms specified. + * + * @param pf + * @param query + * @param fields + * @return + */ + private SearchPredicate getSearchTermsPredicate( + SearchPredicateFactory pf, EdamConceptSearchQueryDto query, String[] fields) { + return pf.simpleQueryString() + .fields(fields) + .matching(query.getSearchTerms()) + .defaultOperator(BooleanOperator.AND) + .toPredicate(); + } + + /** + * Combines the search predicates. + * + * @param pf + * @param predicates + * @return + */ + private SearchPredicate buildTopLevelPredicate( + SearchPredicateFactory pf, List predicates) { + return pf.bool( + b -> { + b.must(f -> f.matchAll()); + for (SearchPredicate predicate : predicates) { + b.must(predicate); + } + }) + .toPredicate(); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/EdamConceptRepository.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/EdamConceptRepository.java new file mode 100644 index 0000000000..d46e4b3228 --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/repository/EdamConceptRepository.java @@ -0,0 +1,7 @@ +package org.sagebionetworks.openchallenges.challenge.service.model.repository; + +import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EdamConceptRepository + extends JpaRepository, CustomEdamConceptRepository {} diff --git a/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/service/EdamConceptService.java b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/service/EdamConceptService.java new file mode 100644 index 0000000000..394f807e0d --- /dev/null +++ b/apps/openchallenges/challenge-service/src/main/java/org/sagebionetworks/openchallenges/challenge/service/service/EdamConceptService.java @@ -0,0 +1,52 @@ +package org.sagebionetworks.openchallenges.challenge.service.service; + +import java.util.Arrays; +import java.util.List; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto; +import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptsPageDto; +import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity; +import org.sagebionetworks.openchallenges.challenge.service.model.mapper.EdamConceptMapper; +import org.sagebionetworks.openchallenges.challenge.service.model.repository.EdamConceptRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EdamConceptService { + + private final EdamConceptRepository edamConceptRepository; + + private EdamConceptMapper edamConceptMapper = new EdamConceptMapper(); + + private static final List SEARCHABLE_FIELDS = + Arrays.asList("class_id", "preferred_label"); + + public EdamConceptService(EdamConceptRepository edamConceptRepository) { + this.edamConceptRepository = edamConceptRepository; + } + + @Transactional(readOnly = true) + public EdamConceptsPageDto listEdamConcepts(EdamConceptSearchQueryDto query) { + Pageable pageable = PageRequest.of(query.getPageNumber(), query.getPageSize()); + + List fieldsToSearchBy = SEARCHABLE_FIELDS; + Page entitiesPage = + edamConceptRepository.findAll(pageable, query, fieldsToSearchBy.toArray(new String[0])); + + List edamConcepts = + edamConceptMapper.convertToDtoList(entitiesPage.getContent()); + + return EdamConceptsPageDto.builder() + .edamConcepts(edamConcepts) + .number(entitiesPage.getNumber()) + .size(entitiesPage.getSize()) + .totalElements(entitiesPage.getTotalElements()) + .totalPages(entitiesPage.getTotalPages()) + .hasNext(entitiesPage.hasNext()) + .hasPrevious(entitiesPage.hasPrevious()) + .build(); + } +} diff --git a/apps/openchallenges/challenge-service/src/main/resources/openapi.yaml b/apps/openchallenges/challenge-service/src/main/resources/openapi.yaml index 771724c1b0..ac08674e13 100644 --- a/apps/openchallenges/challenge-service/src/main/resources/openapi.yaml +++ b/apps/openchallenges/challenge-service/src/main/resources/openapi.yaml @@ -19,6 +19,8 @@ tags: name: ChallengeAnalytics - description: Operations about challenge platforms. name: ChallengePlatform +- description: Operations about EDAM concepts. + name: EdamConcept paths: /challenges: get: @@ -239,6 +241,45 @@ paths: x-accepts: application/json x-tags: - tag: ChallengePlatform + /edamConcepts: + get: + description: List EDAM concepts + operationId: listEdamConcepts + parameters: + - description: The search query used to find EDAM concepts. + explode: true + in: query + name: edamConceptSearchQuery + required: false + schema: + $ref: '#/components/schemas/EdamConceptSearchQuery' + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/EdamConceptsPage' + description: Success + "400": + content: + application/problem+json: + schema: + $ref: '#/components/schemas/BasicError' + description: Invalid request + "500": + content: + application/problem+json: + schema: + $ref: '#/components/schemas/BasicError' + description: The request cannot be fulfilled due to an unexpected server + error + summary: List EDAM concepts + tags: + - EdamConcept + x-accepts: application/json + x-tags: + - tag: EdamConcept components: parameters: challengeSearchQuery: @@ -277,6 +318,15 @@ components: schema: $ref: '#/components/schemas/ChallengePlatformName' style: simple + edamConceptSearchQuery: + description: The search query used to find EDAM concepts. + explode: true + in: query + name: edamConceptSearchQuery + required: false + schema: + $ref: '#/components/schemas/EdamConceptSearchQuery' + style: form responses: BadRequest: content: @@ -974,6 +1024,61 @@ components: type: object x-java-class-annotations: - '@lombok.Builder' + EdamConceptSearchQuery: + description: An EDAM concept search query. + properties: + pageNumber: + default: 0 + description: The page number. + format: int32 + minimum: 0 + type: integer + pageSize: + default: 100 + description: The number of items in a single page. + format: int32 + minimum: 1 + type: integer + searchTerms: + description: A string of search terms used to filter the results. + example: sequence image + type: string + type: object + EdamConceptId: + description: The unique identifier of the EDAM concept. + example: 1 + format: int64 + type: integer + EdamConcept: + description: The EDAM concept. + nullable: true + properties: + id: + description: The unique identifier of the EDAM concept. + example: 1 + format: int64 + type: integer + classId: + example: http://edamontology.org/data_0850 + maxLength: 60 + type: string + preferredLabel: + example: Sequence set + maxLength: 80 + type: string + required: + - classId + - id + - preferredLabel + type: object + EdamConceptsPage: + allOf: + - $ref: '#/components/schemas/PageMetadata' + - $ref: '#/components/schemas/EdamConceptsPage_allOf' + description: A page of EDAM concepts. + type: object + x-java-class-annotations: + - '@lombok.Builder' ChallengesPage_allOf: properties: challenges: @@ -1007,3 +1112,14 @@ components: - challengePlatforms type: object example: null + EdamConceptsPage_allOf: + properties: + edamConcepts: + description: A list of EDAM concepts. + items: + $ref: '#/components/schemas/EdamConcept' + type: array + required: + - edamConcepts + type: object + example: null diff --git a/apps/openchallenges/image-service/build.gradle b/apps/openchallenges/image-service/build.gradle index b66a8ff705..f8d18124d6 100644 --- a/apps/openchallenges/image-service/build.gradle +++ b/apps/openchallenges/image-service/build.gradle @@ -160,11 +160,12 @@ sonar { '**/RFC3339DateFormat.*' ] properties { - property("sonar.projectKey", "openchallenges-image-service") - property("sonar.organization", "sage-bionetworks") property("sonar.host.url", "https://sonarcloud.io") + property("sonar.organization", "sage-bionetworks") + property("sonar.projectKey", "${project.name}") + // We still scan the auto-generated files to be aware of the bugs and code smells they include. + // property("sonar.exclusions", autoGeneratedFiles.join(",")) property("sonar.coverage.exclusions", autoGeneratedFiles.join(",")) - // property("sonar.log.level", "DEBUG") // Include the pull request number (ignored if not set) property("sonar.pullrequest.key", System.getenv('SONAR_PULL_REQUEST_NUMBER')) } diff --git a/libs/openchallenges/api-client-angular/src/lib/.openapi-generator/FILES b/libs/openchallenges/api-client-angular/src/lib/.openapi-generator/FILES index 2c2e40a414..283efffcd3 100644 --- a/libs/openchallenges/api-client-angular/src/lib/.openapi-generator/FILES +++ b/libs/openchallenges/api-client-angular/src/lib/.openapi-generator/FILES @@ -6,6 +6,7 @@ api/challenge.service.ts api/challengeAnalytics.service.ts api/challengeContribution.service.ts api/challengePlatform.service.ts +api/edamConcept.service.ts api/image.service.ts api/organization.service.ts api/user.service.ts @@ -35,6 +36,10 @@ model/challengeSubmissionType.ts model/challengesPage.ts model/challengesPageAllOf.ts model/challengesPerYear.ts +model/edamConcept.ts +model/edamConceptSearchQuery.ts +model/edamConceptsPage.ts +model/edamConceptsPageAllOf.ts model/edamData.ts model/edamOperation.ts model/image.ts diff --git a/libs/openchallenges/api-client-angular/src/lib/api.module.ts b/libs/openchallenges/api-client-angular/src/lib/api.module.ts index 7b4c2d6a93..cca2a77af2 100644 --- a/libs/openchallenges/api-client-angular/src/lib/api.module.ts +++ b/libs/openchallenges/api-client-angular/src/lib/api.module.ts @@ -6,6 +6,7 @@ import { ChallengeService } from './api/challenge.service'; import { ChallengeAnalyticsService } from './api/challengeAnalytics.service'; import { ChallengeContributionService } from './api/challengeContribution.service'; import { ChallengePlatformService } from './api/challengePlatform.service'; +import { EdamConceptService } from './api/edamConcept.service'; import { ImageService } from './api/image.service'; import { OrganizationService } from './api/organization.service'; import { UserService } from './api/user.service'; diff --git a/libs/openchallenges/api-client-angular/src/lib/api/api.ts b/libs/openchallenges/api-client-angular/src/lib/api/api.ts index ccba8f6bf6..13571d0efc 100644 --- a/libs/openchallenges/api-client-angular/src/lib/api/api.ts +++ b/libs/openchallenges/api-client-angular/src/lib/api/api.ts @@ -6,10 +6,12 @@ export * from './challengeContribution.service'; import { ChallengeContributionService } from './challengeContribution.service'; export * from './challengePlatform.service'; import { ChallengePlatformService } from './challengePlatform.service'; +export * from './edamConcept.service'; +import { EdamConceptService } from './edamConcept.service'; export * from './image.service'; import { ImageService } from './image.service'; export * from './organization.service'; import { OrganizationService } from './organization.service'; export * from './user.service'; import { UserService } from './user.service'; -export const APIS = [ChallengeService, ChallengeAnalyticsService, ChallengeContributionService, ChallengePlatformService, ImageService, OrganizationService, UserService]; +export const APIS = [ChallengeService, ChallengeAnalyticsService, ChallengeContributionService, ChallengePlatformService, EdamConceptService, ImageService, OrganizationService, UserService]; diff --git a/libs/openchallenges/api-client-angular/src/lib/api/edamConcept.service.ts b/libs/openchallenges/api-client-angular/src/lib/api/edamConcept.service.ts new file mode 100644 index 0000000000..8c8eecdf76 --- /dev/null +++ b/libs/openchallenges/api-client-angular/src/lib/api/edamConcept.service.ts @@ -0,0 +1,162 @@ +/** + * OpenChallenges REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { BasicError } from '../model/basicError'; +// @ts-ignore +import { EdamConceptSearchQuery } from '../model/edamConceptSearchQuery'; +// @ts-ignore +import { EdamConceptsPage } from '../model/edamConceptsPage'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class EdamConceptService { + + protected basePath = 'http://localhost/v1'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + if (Array.isArray(basePath) && basePath.length > 0) { + basePath = basePath[0]; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substr(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}[${k}]` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * List EDAM concepts + * List EDAM concepts + * @param edamConceptSearchQuery The search query used to find EDAM concepts. + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public listEdamConcepts(edamConceptSearchQuery?: EdamConceptSearchQuery, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable; + public listEdamConcepts(edamConceptSearchQuery?: EdamConceptSearchQuery, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable>; + public listEdamConcepts(edamConceptSearchQuery?: EdamConceptSearchQuery, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable>; + public listEdamConcepts(edamConceptSearchQuery?: EdamConceptSearchQuery, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable { + + let localVarQueryParameters = new HttpParams({encoder: this.encoder}); + if (edamConceptSearchQuery !== undefined && edamConceptSearchQuery !== null) { + localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, + edamConceptSearchQuery, 'edamConceptSearchQuery'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json', + 'application/problem+json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/edamConcepts`; + return this.httpClient.get(`${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + params: localVarQueryParameters, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + +} diff --git a/libs/openchallenges/api-client-angular/src/lib/model/edamConcept.ts b/libs/openchallenges/api-client-angular/src/lib/model/edamConcept.ts new file mode 100644 index 0000000000..fd776bccf0 --- /dev/null +++ b/libs/openchallenges/api-client-angular/src/lib/model/edamConcept.ts @@ -0,0 +1,25 @@ +/** + * OpenChallenges REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * The EDAM concept. + */ +export interface EdamConcept { + /** + * The unique identifier of the EDAM concept. + */ + id: number; + classId: string; + preferredLabel: string; +} + diff --git a/libs/openchallenges/api-client-angular/src/lib/model/edamConceptSearchQuery.ts b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptSearchQuery.ts new file mode 100644 index 0000000000..d62ec1f9ad --- /dev/null +++ b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptSearchQuery.ts @@ -0,0 +1,31 @@ +/** + * OpenChallenges REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * An EDAM concept search query. + */ +export interface EdamConceptSearchQuery { + /** + * The page number. + */ + pageNumber?: number; + /** + * The number of items in a single page. + */ + pageSize?: number; + /** + * A string of search terms used to filter the results. + */ + searchTerms?: string; +} + diff --git a/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPage.ts b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPage.ts new file mode 100644 index 0000000000..44a9bf3d8b --- /dev/null +++ b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPage.ts @@ -0,0 +1,48 @@ +/** + * OpenChallenges REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { EdamConcept } from './edamConcept'; + + +/** + * A page of EDAM concepts. + */ +export interface EdamConceptsPage { + /** + * The page number. + */ + number: number; + /** + * The number of items in a single page. + */ + size: number; + /** + * Total number of elements in the result set. + */ + totalElements: number; + /** + * Total number of pages in the result set. + */ + totalPages: number; + /** + * Returns if there is a next page. + */ + hasNext: boolean; + /** + * Returns if there is a previous page. + */ + hasPrevious: boolean; + /** + * A list of EDAM concepts. + */ + edamConcepts: Array; +} + diff --git a/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPageAllOf.ts b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPageAllOf.ts new file mode 100644 index 0000000000..095c1f4c58 --- /dev/null +++ b/libs/openchallenges/api-client-angular/src/lib/model/edamConceptsPageAllOf.ts @@ -0,0 +1,21 @@ +/** + * OpenChallenges REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { EdamConcept } from './edamConcept'; + + +export interface EdamConceptsPageAllOf { + /** + * A list of EDAM concepts. + */ + edamConcepts: Array; +} + diff --git a/libs/openchallenges/api-client-angular/src/lib/model/models.ts b/libs/openchallenges/api-client-angular/src/lib/model/models.ts index 9e8ff3a35d..4351c231ed 100644 --- a/libs/openchallenges/api-client-angular/src/lib/model/models.ts +++ b/libs/openchallenges/api-client-angular/src/lib/model/models.ts @@ -20,6 +20,10 @@ export * from './challengeSubmissionType'; export * from './challengesPage'; export * from './challengesPageAllOf'; export * from './challengesPerYear'; +export * from './edamConcept'; +export * from './edamConceptSearchQuery'; +export * from './edamConceptsPage'; +export * from './edamConceptsPageAllOf'; export * from './edamData'; export * from './edamOperation'; export * from './image'; diff --git a/libs/openchallenges/api-description/build/challenge.openapi.yaml b/libs/openchallenges/api-description/build/challenge.openapi.yaml index 3fc81c16cd..8ead8378d9 100644 --- a/libs/openchallenges/api-description/build/challenge.openapi.yaml +++ b/libs/openchallenges/api-description/build/challenge.openapi.yaml @@ -19,6 +19,8 @@ tags: description: Operations about challenge analytics. - name: ChallengePlatform description: Operations about challenge platforms. + - name: EdamConcept + description: Operations about EDAM concepts. paths: /challenges: get: @@ -136,6 +138,26 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' + /edamConcepts: + get: + tags: + - EdamConcept + summary: List EDAM concepts + description: List EDAM concepts + operationId: listEdamConcepts + parameters: + - $ref: '#/components/parameters/edamConceptSearchQuery' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EdamConceptsPage' + description: Success + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/InternalServerError' components: schemas: ChallengeSort: @@ -699,6 +721,66 @@ components: - challengePlatforms x-java-class-annotations: - '@lombok.Builder' + EdamConceptSearchQuery: + type: object + description: An EDAM concept search query. + properties: + pageNumber: + description: The page number. + type: integer + format: int32 + default: 0 + minimum: 0 + pageSize: + description: The number of items in a single page. + type: integer + format: int32 + default: 100 + minimum: 1 + searchTerms: + description: A string of search terms used to filter the results. + type: string + example: sequence image + EdamConceptId: + description: The unique identifier of the EDAM concept. + type: integer + format: int64 + example: 1 + EdamConcept: + type: object + description: The EDAM concept. + properties: + id: + $ref: '#/components/schemas/EdamConceptId' + classId: + type: string + example: http://edamontology.org/data_0850 + maxLength: 60 + preferredLabel: + type: string + example: Sequence set + maxLength: 80 + required: + - id + - classId + - preferredLabel + nullable: true + EdamConceptsPage: + type: object + description: A page of EDAM concepts. + allOf: + - $ref: '#/components/schemas/PageMetadata' + - type: object + properties: + edamConcepts: + description: A list of EDAM concepts. + type: array + items: + $ref: '#/components/schemas/EdamConcept' + required: + - edamConcepts + x-java-class-annotations: + - '@lombok.Builder' parameters: challengeSearchQuery: name: challengeSearchQuery @@ -726,6 +808,12 @@ components: required: true schema: $ref: '#/components/schemas/ChallengePlatformName' + edamConceptSearchQuery: + name: edamConceptSearchQuery + description: The search query used to find EDAM concepts. + in: query + schema: + $ref: '#/components/schemas/EdamConceptSearchQuery' responses: BadRequest: description: Invalid request diff --git a/libs/openchallenges/api-description/build/openapi.yaml b/libs/openchallenges/api-description/build/openapi.yaml index 738df04bdd..eff9cf9ea5 100644 --- a/libs/openchallenges/api-description/build/openapi.yaml +++ b/libs/openchallenges/api-description/build/openapi.yaml @@ -19,6 +19,8 @@ tags: description: Operations about challenge analytics. - name: ChallengePlatform description: Operations about challenge platforms. + - name: EdamConcept + description: Operations about EDAM concepts. - name: Image description: Operations about images - name: Organization @@ -142,6 +144,26 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' + /edamConcepts: + get: + tags: + - EdamConcept + summary: List EDAM concepts + description: List EDAM concepts + operationId: listEdamConcepts + parameters: + - $ref: '#/components/parameters/edamConceptSearchQuery' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EdamConceptsPage' + description: Success + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/InternalServerError' /images: get: tags: @@ -851,6 +873,66 @@ components: - challengePlatforms x-java-class-annotations: - '@lombok.Builder' + EdamConceptSearchQuery: + type: object + description: An EDAM concept search query. + properties: + pageNumber: + description: The page number. + type: integer + format: int32 + default: 0 + minimum: 0 + pageSize: + description: The number of items in a single page. + type: integer + format: int32 + default: 100 + minimum: 1 + searchTerms: + description: A string of search terms used to filter the results. + type: string + example: sequence image + EdamConceptId: + description: The unique identifier of the EDAM concept. + type: integer + format: int64 + example: 1 + EdamConcept: + type: object + description: The EDAM concept. + properties: + id: + $ref: '#/components/schemas/EdamConceptId' + classId: + type: string + example: 'http://edamontology.org/data_0850' + maxLength: 60 + preferredLabel: + type: string + example: Sequence set + maxLength: 80 + required: + - id + - classId + - preferredLabel + nullable: true + EdamConceptsPage: + type: object + description: A page of EDAM concepts. + allOf: + - $ref: '#/components/schemas/PageMetadata' + - type: object + properties: + edamConcepts: + description: A list of EDAM concepts. + type: array + items: + $ref: '#/components/schemas/EdamConcept' + required: + - edamConcepts + x-java-class-annotations: + - '@lombok.Builder' ImageKey: description: The unique identifier of the image. type: string @@ -1207,6 +1289,12 @@ components: required: true schema: $ref: '#/components/schemas/ChallengePlatformName' + edamConceptSearchQuery: + name: edamConceptSearchQuery + description: The search query used to find EDAM concepts. + in: query + schema: + $ref: '#/components/schemas/EdamConceptSearchQuery' imageQuery: name: imageQuery description: The query used to get an image. diff --git a/libs/openchallenges/api-description/src/challenge.openapi.yaml b/libs/openchallenges/api-description/src/challenge.openapi.yaml index 94d40243e8..14ed5954ed 100644 --- a/libs/openchallenges/api-description/src/challenge.openapi.yaml +++ b/libs/openchallenges/api-description/src/challenge.openapi.yaml @@ -19,6 +19,8 @@ tags: description: Operations about challenge analytics. - name: ChallengePlatform description: Operations about challenge platforms. + - name: EdamConcept + description: Operations about EDAM concepts. paths: /challenges: $ref: paths/challenges.yaml @@ -32,3 +34,5 @@ paths: $ref: paths/challengePlatforms.yaml /challengePlatforms/{challengePlatformName}: $ref: paths/challengePlatforms/@{challengePlatformName}.yaml + /edamConcepts: + $ref: paths/edamConcepts.yaml diff --git a/libs/openchallenges/api-description/src/components/parameters/query/edamConceptSearchQuery.yaml b/libs/openchallenges/api-description/src/components/parameters/query/edamConceptSearchQuery.yaml new file mode 100644 index 0000000000..22be289252 --- /dev/null +++ b/libs/openchallenges/api-description/src/components/parameters/query/edamConceptSearchQuery.yaml @@ -0,0 +1,5 @@ +name: edamConceptSearchQuery +description: The search query used to find EDAM concepts. +in: query +schema: + $ref: ../../schemas/EdamConceptSearchQuery.yaml diff --git a/libs/openchallenges/api-description/src/components/schemas/EdamConcept.yaml b/libs/openchallenges/api-description/src/components/schemas/EdamConcept.yaml new file mode 100644 index 0000000000..880d20cd68 --- /dev/null +++ b/libs/openchallenges/api-description/src/components/schemas/EdamConcept.yaml @@ -0,0 +1,18 @@ +type: object +description: The EDAM concept. +properties: + id: + $ref: EdamConceptId.yaml + classId: + type: string + example: 'http://edamontology.org/data_0850' + maxLength: 60 + preferredLabel: + type: string + example: 'Sequence set' + maxLength: 80 +required: + - id + - classId + - preferredLabel +nullable: true diff --git a/libs/openchallenges/api-description/src/components/schemas/EdamConceptId.yaml b/libs/openchallenges/api-description/src/components/schemas/EdamConceptId.yaml new file mode 100644 index 0000000000..ccb13d51ef --- /dev/null +++ b/libs/openchallenges/api-description/src/components/schemas/EdamConceptId.yaml @@ -0,0 +1,4 @@ +description: The unique identifier of the EDAM concept. +type: integer +format: int64 +example: 1 diff --git a/libs/openchallenges/api-description/src/components/schemas/EdamConceptSearchQuery.yaml b/libs/openchallenges/api-description/src/components/schemas/EdamConceptSearchQuery.yaml new file mode 100644 index 0000000000..baa950dd7e --- /dev/null +++ b/libs/openchallenges/api-description/src/components/schemas/EdamConceptSearchQuery.yaml @@ -0,0 +1,19 @@ +type: object +description: An EDAM concept search query. +properties: + pageNumber: + description: The page number. + type: integer + format: int32 + default: 0 + minimum: 0 + pageSize: + description: The number of items in a single page. + type: integer + format: int32 + default: 100 + minimum: 1 + searchTerms: + description: A string of search terms used to filter the results. + type: string + example: 'sequence image' diff --git a/libs/openchallenges/api-description/src/components/schemas/EdamConceptsPage.yaml b/libs/openchallenges/api-description/src/components/schemas/EdamConceptsPage.yaml new file mode 100644 index 0000000000..ee8ccc193d --- /dev/null +++ b/libs/openchallenges/api-description/src/components/schemas/EdamConceptsPage.yaml @@ -0,0 +1,15 @@ +type: object +description: A page of EDAM concepts. +allOf: + - $ref: PageMetadata.yaml + - type: object + properties: + edamConcepts: + description: A list of EDAM concepts. + type: array + items: + $ref: EdamConcept.yaml + required: + - edamConcepts +x-java-class-annotations: + - '@lombok.Builder' diff --git a/libs/openchallenges/api-description/src/paths/edamConcepts.yaml b/libs/openchallenges/api-description/src/paths/edamConcepts.yaml new file mode 100644 index 0000000000..70c96cdc7c --- /dev/null +++ b/libs/openchallenges/api-description/src/paths/edamConcepts.yaml @@ -0,0 +1,19 @@ +get: + tags: + - EdamConcept + summary: List EDAM concepts + description: List EDAM concepts + operationId: listEdamConcepts + parameters: + - $ref: ../components/parameters/query/edamConceptSearchQuery.yaml + responses: + '200': + content: + application/json: + schema: + $ref: ../components/schemas/EdamConceptsPage.yaml + description: Success + '400': + $ref: ../components/responses/BadRequest.yaml + '500': + $ref: ../components/responses/InternalServerError.yaml