fix:sample/plate 之前的开发

This commit is contained in:
彭帅
2026-05-28 11:56:17 +08:00
parent fc36bc83e3
commit 8b65de36b8
367 changed files with 57752 additions and 947 deletions

View File

@@ -5,12 +5,16 @@
*/
package io.swagger.api.core;
import io.swagger.model.core.CommonCropListResponse;
import io.swagger.model.core.CommonCropNamesResponse;
import io.swagger.model.core.CommonCropNewRequest;
import jakarta.validation.Valid;
import io.swagger.annotations.*;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -33,4 +37,44 @@ public interface CommonCropNamesApi {
@ApiParam(value = "The size of the pages to be returned. Default is `1000`.") @Valid @RequestParam(value = "pageSize", required = false) Integer pageSize,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
@ApiOperation(value = "Create a Common Crop", nickname = "commoncropnamesCropsPost", notes = "Add a new common crop name to the database", response = CommonCropListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Common Crop Names", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = CommonCropListResponse.class),
@ApiResponse(code = 400, message = "Bad Request", response = String.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/commoncropnames/crops", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
ResponseEntity<CommonCropListResponse> commoncropnamesCropsPost(
@ApiParam(value = "Common crop to create", required = true) @Valid @RequestBody CommonCropNewRequest body,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
@ApiOperation(value = "Update a Common Crop", nickname = "commoncropnamesCropsCropDbIdPut", notes = "Update an existing common crop name", response = CommonCropListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Common Crop Names", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = CommonCropListResponse.class),
@ApiResponse(code = 400, message = "Bad Request", response = String.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/commoncropnames/crops/{cropDbId}", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.PUT)
ResponseEntity<CommonCropListResponse> commoncropnamesCropsCropDbIdPut(
@ApiParam(value = "Crop identifier (common crop name or database id)", required = true) @PathVariable("cropDbId") String cropDbId,
@ApiParam(value = "Updated common crop name", required = true) @Valid @RequestBody CommonCropNewRequest body,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
@ApiOperation(value = "Delete a Common Crop", nickname = "commoncropnamesCropsCropDbIdDelete", notes = "Delete a common crop name from the database", response = CommonCropListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Common Crop Names", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = CommonCropListResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/commoncropnames/crops/{cropDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<CommonCropListResponse> commoncropnamesCropsCropDbIdDelete(
@ApiParam(value = "Crop identifier (common crop name or database id)", required = true) @PathVariable("cropDbId") String cropDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
}

View File

@@ -80,6 +80,19 @@ public interface LocationsApi {
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Location", nickname = "locationsLocationDbIdDelete", notes = "Delete an existing location", response = LocationSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Locations", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = LocationSingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/locations/{locationDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<LocationSingleResponse> locationsLocationDbIdDelete(
@ApiParam(value = "The internal DB id for a location", required = true) @PathVariable("locationDbId") String locationDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Create new Locations", nickname = "locationsPost", notes = "Add new locations to database * The `countryCode` is as per [ISO_3166-1_alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) spec. * `altitude` is in meters.", response = LocationListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Locations", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = LocationListResponse.class),

View File

@@ -79,6 +79,19 @@ public interface PeopleApi {
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Person", nickname = "peoplePersonDbIdDelete", notes = "Delete an existing Person", response = PersonSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "People", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = PersonSingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/people/{personDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<PersonSingleResponse> peoplePersonDbIdDelete(
@ApiParam(value = "The unique ID of a person", required = true) @PathVariable("personDbId") String personDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Create new People", nickname = "peoplePost", notes = "Create new People entities. `personDbId` is generated and managed by the server.", response = PersonListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "People", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = PersonListResponse.class),

View File

@@ -77,4 +77,16 @@ public interface SeasonsApi {
@ApiParam(value = "") @Valid @RequestBody Season body,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Season", nickname = "seasonsSeasonDbIdDelete", notes = "Delete an existing Season", response = SeasonSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Seasons", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = SeasonSingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/seasons/{seasonDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<SeasonSingleResponse> seasonsSeasonDbIdDelete(
@ApiParam(value = "The unique identifier for a season. For backward compatibility it can be a string like '2012', '1957-2004'", required = true) @PathVariable("seasonDbId") String seasonDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
}

View File

@@ -12,12 +12,15 @@ import io.swagger.annotations.*;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import io.swagger.model.germ.BreedingMethod;
import jakarta.validation.Valid;
import java.util.List;
@javax.annotation.processing.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2020-03-20T16:33:36.513Z[GMT]")
@Api(value = "breedingmethods", description = "the breedingmethods API")
@@ -50,4 +53,43 @@ public interface BreedingMethodsApi {
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "POST new Breeding Methods", nickname = "breedingmethodsPost", notes = "Add new breeding method entries to the database", response = BreedingMethodListResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Germplasm", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = BreedingMethodListResponse.class),
@ApiResponse(code = 400, message = "Bad Request", response = String.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class) })
@RequestMapping(value = "/breedingmethods", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
ResponseEntity<BreedingMethodListResponse> breedingmethodsPost(@ApiParam(value = "") @Valid @RequestBody List<BreedingMethod> body,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Update existing Breeding Method", nickname = "breedingmethodsBreedingMethodDbIdPut", notes = "Update existing Breeding Method", response = BreedingMethodSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Germplasm", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = BreedingMethodSingleResponse.class),
@ApiResponse(code = 400, message = "Bad Request", response = String.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class) })
@RequestMapping(value = "/breedingmethods/{breedingMethodDbId}", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.PUT)
ResponseEntity<BreedingMethodSingleResponse> breedingmethodsBreedingMethodDbIdPut(
@ApiParam(value = "Internal database identifier for a breeding method", required = true) @PathVariable("breedingMethodDbId") String breedingMethodDbId,
@ApiParam(value = "") @Valid @RequestBody BreedingMethod body,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Breeding Method", nickname = "breedingmethodsBreedingMethodDbIdDelete", notes = "Delete an existing Breeding Method", response = BreedingMethodSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Germplasm", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = BreedingMethodSingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/breedingmethods/{breedingMethodDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<BreedingMethodSingleResponse> breedingmethodsBreedingMethodDbIdDelete(
@ApiParam(value = "Internal database identifier for a breeding method", required = true) @PathVariable("breedingMethodDbId") String breedingMethodDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
}

View File

@@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import jakarta.validation.Valid;
@@ -80,4 +81,16 @@ public interface OntologiesApi {
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization,
@ApiParam(value = "") @Valid @RequestBody List<OntologyNewRequest> body) throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Ontology record", nickname = "ontologiesOntologyDbIdDelete", notes = "Delete an existing Ontology record", response = OntologySingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Ontologies", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = OntologySingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/ontologies/{ontologyDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<OntologySingleResponse> ontologiesOntologyDbIdDelete(
@ApiParam(value = "The unique identifier for an ontology definition. Use this parameter to filter results based on a specific ontology Use `GET /ontologies` to find the list of available ontologies on a server.", required = true) @PathVariable("ontologyDbId") String ontologyDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException;
}

View File

@@ -87,4 +87,17 @@ public interface TraitsApi {
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
@ApiOperation(value = "Delete an existing Trait", nickname = "traitsTraitDbIdDelete", notes = "Delete an existing trait", response = TraitSingleResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Traits", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = TraitSingleResponse.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class),
@ApiResponse(code = 404, message = "Not Found", response = String.class),
@ApiResponse(code = 409, message = "Conflict", response = String.class) })
@RequestMapping(value = "/traits/{traitDbId}", produces = { "application/json" }, method = RequestMethod.DELETE)
ResponseEntity<TraitSingleResponse> traitsTraitDbIdDelete(
@ApiParam(value = "Id of the trait to delete.", required = true) @PathVariable("traitDbId") String traitDbId,
@ApiParam(value = "HTTP HEADER - Token used for Authorization <strong> Bearer {token_string} </strong>") @RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException;
}

View File

@@ -0,0 +1,63 @@
package io.swagger.model.core;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.validation.annotation.Validated;
import java.util.Objects;
@Validated
public class CommonCrop {
@JsonProperty("cropDbId")
private String cropDbId = null;
@JsonProperty("commonCropName")
private String commonCropName = null;
public CommonCrop cropDbId(String cropDbId) {
this.cropDbId = cropDbId;
return this;
}
@ApiModelProperty(example = "Tomatillo", value = "Crop identifier; uses common crop name for compatibility with GET /commoncropnames")
public String getCropDbId() {
return cropDbId;
}
public void setCropDbId(String cropDbId) {
this.cropDbId = cropDbId;
}
public CommonCrop commonCropName(String commonCropName) {
this.commonCropName = commonCropName;
return this;
}
@ApiModelProperty(example = "Tomatillo", required = true, value = "Common crop name")
public String getCommonCropName() {
return commonCropName;
}
public void setCommonCropName(String commonCropName) {
this.commonCropName = commonCropName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CommonCrop commonCrop = (CommonCrop) o;
return Objects.equals(cropDbId, commonCrop.cropDbId)
&& Objects.equals(commonCropName, commonCrop.commonCropName);
}
@Override
public int hashCode() {
return Objects.hash(cropDbId, commonCropName);
}
}

View File

@@ -0,0 +1,82 @@
package io.swagger.model.core;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.model.BrAPIResponse;
import io.swagger.model.Context;
import io.swagger.model.Metadata;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import java.util.Objects;
@Validated
public class CommonCropListResponse implements BrAPIResponse<CommonCropListResponseResult> {
@JsonProperty("@context")
private Context _atContext = null;
@JsonProperty("metadata")
private Metadata metadata = null;
@JsonProperty("result")
private CommonCropListResponseResult result = null;
public CommonCropListResponse _atContext(Context _atContext) {
this._atContext = _atContext;
return this;
}
public void set_atContext(Context _atContext) {
this._atContext = _atContext;
}
public CommonCropListResponse metadata(Metadata metadata) {
this.metadata = metadata;
return this;
}
@ApiModelProperty(required = true, value = "")
@Valid
public Metadata getMetadata() {
return metadata;
}
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
public CommonCropListResponse result(CommonCropListResponseResult result) {
this.result = result;
return this;
}
@ApiModelProperty(required = true, value = "")
@Valid
public CommonCropListResponseResult getResult() {
return result;
}
public void setResult(CommonCropListResponseResult result) {
this.result = result;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CommonCropListResponse that = (CommonCropListResponse) o;
return Objects.equals(_atContext, that._atContext)
&& Objects.equals(metadata, that.metadata)
&& Objects.equals(result, that.result);
}
@Override
public int hashCode() {
return Objects.hash(_atContext, metadata, result);
}
}

View File

@@ -0,0 +1,56 @@
package io.swagger.model.core;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.model.BrAPIResponseResult;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Validated
public class CommonCropListResponseResult implements BrAPIResponseResult<CommonCrop> {
@JsonProperty("data")
@Valid
private List<CommonCrop> data = new ArrayList<>();
public CommonCropListResponseResult data(List<CommonCrop> data) {
this.data = data;
return this;
}
public CommonCropListResponseResult addDataItem(CommonCrop dataItem) {
this.data.add(dataItem);
return this;
}
@ApiModelProperty(required = true, value = "Crop mutation results")
@Valid
public List<CommonCrop> getData() {
return data;
}
public void setData(List<CommonCrop> data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CommonCropListResponseResult that = (CommonCropListResponseResult) o;
return Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(data);
}
}

View File

@@ -0,0 +1,45 @@
package io.swagger.model.core;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.validation.annotation.Validated;
import java.util.Objects;
@Validated
public class CommonCropNewRequest {
@JsonProperty("commonCropName")
private String commonCropName = null;
public CommonCropNewRequest commonCropName(String commonCropName) {
this.commonCropName = commonCropName;
return this;
}
@ApiModelProperty(example = "Maize", required = true, value = "Common crop name")
public String getCommonCropName() {
return commonCropName;
}
public void setCommonCropName(String commonCropName) {
this.commonCropName = commonCropName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CommonCropNewRequest that = (CommonCropNewRequest) o;
return Objects.equals(commonCropName, that.commonCropName);
}
@Override
public int hashCode() {
return Objects.hash(commonCropName);
}
}

View File

@@ -0,0 +1,49 @@
package io.swagger.model.geno;
public class ReferenceBasesPage {
private String referenceBasesDbId;
private String referenceDbId;
private String referenceName;
private Integer pageNumber;
private String bases;
public String getReferenceBasesDbId() {
return referenceBasesDbId;
}
public void setReferenceBasesDbId(String referenceBasesDbId) {
this.referenceBasesDbId = referenceBasesDbId;
}
public String getReferenceDbId() {
return referenceDbId;
}
public void setReferenceDbId(String referenceDbId) {
this.referenceDbId = referenceDbId;
}
public String getReferenceName() {
return referenceName;
}
public void setReferenceName(String referenceName) {
this.referenceName = referenceName;
}
public Integer getPageNumber() {
return pageNumber;
}
public void setPageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
}
public String getBases() {
return bases;
}
public void setBases(String bases) {
this.bases = bases;
}
}

View File

@@ -0,0 +1,35 @@
package io.swagger.model.geno;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.model.BrAPIResponse;
import io.swagger.model.Context;
import io.swagger.model.Metadata;
public class ReferenceBasesPageListResponse implements BrAPIResponse<ReferenceBasesPageListResponseResult> {
@JsonProperty("@context")
private Context _atContext;
private Metadata metadata;
private ReferenceBasesPageListResponseResult result;
public void set_atContext(Context _atContext) {
this._atContext = _atContext;
}
public Metadata getMetadata() {
return metadata;
}
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
public ReferenceBasesPageListResponseResult getResult() {
return result;
}
public void setResult(ReferenceBasesPageListResponseResult result) {
this.result = result;
}
}

View File

@@ -0,0 +1,17 @@
package io.swagger.model.geno;
import java.util.List;
import io.swagger.model.BrAPIResponseResult;
public class ReferenceBasesPageListResponseResult implements BrAPIResponseResult<ReferenceBasesPage> {
private List<ReferenceBasesPage> data;
public List<ReferenceBasesPage> getData() {
return data;
}
public void setData(List<ReferenceBasesPage> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,35 @@
package io.swagger.model.geno;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.model.BrAPIResponse;
import io.swagger.model.Context;
import io.swagger.model.Metadata;
public class ReferenceBasesPageSingleResponse implements BrAPIResponse<ReferenceBasesPage> {
@JsonProperty("@context")
private Context _atContext;
private Metadata metadata;
private ReferenceBasesPage result;
public void set_atContext(Context _atContext) {
this._atContext = _atContext;
}
public Metadata getMetadata() {
return metadata;
}
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
public ReferenceBasesPage getResult() {
return result;
}
public void setResult(ReferenceBasesPage result) {
this.result = result;
}
}

View File

@@ -1,6 +1,6 @@
package org.brapi.test.BrAPITestServer.auth;
import org.springframework.beans.factory.annotation.Value;
import org.brapi.test.BrAPITestServer.system.security.SystemJwtAuthFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@@ -11,40 +11,26 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableWebSecurity
@EnableMethodSecurity
public class BrapiTestServerAuthConfig {
@Value( "${security.oidc_discovery_url}" )
private String oidcDiscoveryUrl;
@Value("${security.issuer_url}")private String issuerUrl;
@Value( "${security.enabled:true}" )
private boolean authEnabled;
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
public SecurityFilterChain filterChain(HttpSecurity http, SystemJwtAuthFilter systemJwtAuthFilter) throws Exception {
http.csrf(CsrfConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.anyRequest()
.permitAll() //TODO: secure this
) //.authenticated().and()
.addFilter(new BrapiTestServerJWTAuthFilter(
authenticationManager,
oidcDiscoveryUrl,
issuerUrl,
authEnabled))
// this disables session creation on Spring Security
.sessionManagement(sm -> sm
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
.requestMatchers("/auth/**").permitAll()
.anyRequest().permitAll())
.addFilterBefore(systemJwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}

View File

@@ -1,225 +0,0 @@
package org.brapi.test.BrAPITestServer.auth;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.node.ArrayNode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class BrapiTestServerJWTAuthFilter extends BasicAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(BrapiTestServerJWTAuthFilter.class);
private static final List<String> ADMIN_IDS = Arrays.asList("dummyAdmin", "ps664@cornell.edu");
private final String oidcDiscoveryUrl;
private final String issuerUrl;
private final boolean authEnabled;
public BrapiTestServerJWTAuthFilter(AuthenticationManager authManager,
String oidcDiscoveryUrl,
String issuerUrl,
boolean authEnabled) {
super(authManager);
this.oidcDiscoveryUrl = oidcDiscoveryUrl;
this.issuerUrl = issuerUrl;
this.authEnabled = authEnabled;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader("Authorization");
//Auth disabled by config property
if (!authEnabled) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
AuthDetails bypassDetails = new AuthDetails();
bypassDetails.setUserId("anonymousUser");
bypassDetails.setExpirationTimestamp(Long.MAX_VALUE);
bypassDetails.setRoles(new ArrayList<>());
bypassDetails.getRoles().add("ROLE_USER");
bypassDetails.getRoles().add("ROLE_ADMIN");
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(bypassDetails.getUserId(), null, authorities);
authentication.setDetails(bypassDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
return;
}
// Anonymous User
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(req, res);
return;
}
// Token Available
try {
String token = header.replaceFirst("Bearer ", "");
AuthDetails userDetails = validateToken(token);
if (userDetails != null) {
List<GrantedAuthority> authorities = getAuthorities(userDetails);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails.getUserId(), null, authorities);
authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new GeneralSecurityException("Auth Error");
}
chain.doFilter(req, res);
} catch (GeneralSecurityException e) {
String msg = determineExceptionCause(e);
log.error(msg);
res.addHeader("WWW-Authenticate", "Basic realm=\"\"");
res.setStatus(HttpStatus.UNAUTHORIZED.value());
res.setContentType("text/plain;charset=UTF-8");
res.getWriter().print(msg);
res.getWriter().flush();
// res.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase() + " - " + msg);
}
}
private String determineExceptionCause(GeneralSecurityException e) {
String msg = HttpStatus.UNAUTHORIZED.toString() + " - ";
Throwable exception = e;
msg += exception.getMessage();
while (exception.getCause() != null){
exception = exception.getCause();
msg += " - " + exception.getMessage();
}
msg += "\nPlease go to https://brapi.org/oauth and login to generate a fresh token, or use the dummy token 'XXXX'";
return "\"" + msg + "\"";
}
private AuthDetails validateToken(String token)
throws FileNotFoundException, IOException, GeneralSecurityException {
AuthDetails userDetails = null;
if (token.equals("XXXX") || token.equals("YYYY") || token.equals("ZZZZ")) {
userDetails = validateDummyToken(token);
} else {
userDetails = validateOAuthToken(token);
}
return userDetails;
}
private AuthDetails validateDummyToken(String token) {
AuthDetails details = null;
if (token.equals("XXXX")) {
details = new AuthDetails();
details.setUserId("dummy");
details.setExpirationTimestamp(Long.MAX_VALUE);
} else if (token.equals("YYYY")) {
details = new AuthDetails();
details.setUserId("dummyAdmin");
details.setExpirationTimestamp(Long.MAX_VALUE);
} else if (token.equals("ZZZZ")) {
details = new AuthDetails();
details.setUserId("anonymousUser");
details.setExpirationTimestamp(Long.MAX_VALUE);
}
return details;
}
private AuthDetails validateOAuthToken(String token) throws GeneralSecurityException {
try {
token = token.replaceFirst("Bearer ", "");
RSAPublicKey pubKey = getPublicKey(oidcDiscoveryUrl);
Algorithm algorithm = Algorithm.RSA256(pubKey, null);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(issuerUrl)
.build();
DecodedJWT jwt = verifier.verify(token);
AuthDetails details = new AuthDetails();
details.setUserId(jwt.getClaim("email").asString());
details.setExpirationTimestamp(jwt.getExpiresAt());
return details;
} catch (JWTVerificationException e) {
throw new GeneralSecurityException("Invalid JWT", e);
} catch (Exception e) {
throw new GeneralSecurityException("JWT Verification Process Failed", e);
}
}
private RSAPublicKey getPublicKey(String discoveryURL) {
try {
JsonNode discovery = (new ObjectMapper()).readTree(new URL(discoveryURL));
String jwksURL = discovery.findValue("jwks_uri").asText();
ArrayNode jwks = (new ObjectMapper()).readTree(new URL(jwksURL)).withArray("keys");
String keyVal = null;
for(JsonNode jwk: jwks){
String algo = jwk.findValue("alg").asText();
if(algo.equals("RS256")){
keyVal = jwk.findValue("x5c").get(0).asText();
}
}
String certb64 = keyVal;
byte[] certder = Base64.decodeBase64(certb64);
InputStream certstream = new ByteArrayInputStream(certder);
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(certstream);
RSAPublicKey pubKey = (RSAPublicKey) cert.getPublicKey();
return pubKey;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (CertificateException e) {
e.printStackTrace();
return null;
}
}
private List<GrantedAuthority> getAuthorities(AuthDetails userDetails) {
List<GrantedAuthority> auth = new ArrayList<>();
if (userDetails != null) {
GrantedAuthority user = new SimpleGrantedAuthority("ROLE_USER");
auth.add(user);
userDetails.addRole("ROLE_USER");
if (ADMIN_IDS.contains(userDetails.getUserId())) {
GrantedAuthority admin = new SimpleGrantedAuthority("ROLE_ADMIN");
auth.add(admin);
userDetails.addRole("ROLE_ADMIN");
}
}
return auth;
}
}

View File

@@ -1,59 +0,0 @@
package org.brapi.test.BrAPITestServer.auth.oldTokens;
import io.swagger.model.core.ServerInfoResponse;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import org.brapi.test.BrAPITestServer.controller.core.BrAPIController;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.servlet.http.HttpServletRequest;
@javax.annotation.processing.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2020-03-20T16:31:52.030Z[GMT]")
@Controller
public class TokenController extends BrAPIController {
private static final Logger log = LoggerFactory.getLogger(TokenController.class);
private final HttpServletRequest request;
@org.springframework.beans.factory.annotation.Autowired
public TokenController(HttpServletRequest request) {
this.request = request;
}
@ApiOperation(value = "Get the list of implemented Calls", nickname = "tokenPost", response = ServerInfoResponse.class, authorizations = {
@Authorization(value = "AuthorizationToken") }, tags = { "Server Info", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ServerInfoResponse.class),
@ApiResponse(code = 400, message = "Bad Request", response = String.class),
@ApiResponse(code = 401, message = "Unauthorized", response = String.class),
@ApiResponse(code = 403, message = "Forbidden", response = String.class) })
@RequestMapping(value = "/token", produces = { "application/json" }, method = RequestMethod.POST)
@CrossOrigin
public ResponseEntity<TokenResponse> tokenPost(@RequestBody TokenRequest body)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_ANONYMOUS", "ROLE_USER");
validateAcceptHeader(request);
return new ResponseEntity<TokenResponse>(new TokenResponse(), HttpStatus.OK);
}
private class TokenResponse{
@JsonProperty("access_token")
private final String accessToken = "YYYY";
}
}

View File

@@ -1,33 +0,0 @@
package org.brapi.test.BrAPITestServer.auth.oldTokens;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TokenRequest {
@JsonProperty("username")
private Optional<String> username = null;
@JsonProperty("password")
private Optional<String> password = null;
public TokenRequest() {
// TODO Auto-generated constructor stub
}
public Optional<String> getUsername() {
return username;
}
public void setUsername(Optional<String> username) {
this.username = username;
}
public Optional<String> getPassword() {
return password;
}
public void setPassword(Optional<String> password) {
this.password = password;
}
}

View File

@@ -0,0 +1,23 @@
package org.brapi.test.BrAPITestServer.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class BrApiWebConfig implements WebMvcConfigurer {
private static final String BRAPI_PREFIX = "/brapi/v2";
private static final String BRAPI_CONTROLLER_PACKAGE = "org.brapi.test.BrAPITestServer.controller";
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(BRAPI_PREFIX, BrApiWebConfig::isBrApiController);
}
static boolean isBrApiController(Class<?> controllerClass) {
return controllerClass.isAnnotationPresent(RestController.class)
&& controllerClass.getPackageName().startsWith(BRAPI_CONTROLLER_PACKAGE);
}
}

View File

@@ -0,0 +1,17 @@
package org.brapi.test.BrAPITestServer.config;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OpenApiResourceController {
@GetMapping(value = "/brapi/v2/openapi.json", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Resource> openApiDocument() {
return ResponseEntity.ok(new ClassPathResource("static/openapi.json"));
}
}

View File

@@ -1,8 +1,12 @@
package org.brapi.test.BrAPITestServer.controller.core;
import io.swagger.model.Metadata;
import io.swagger.model.core.CommonCrop;
import io.swagger.model.core.CommonCropListResponse;
import io.swagger.model.core.CommonCropListResponseResult;
import io.swagger.model.core.CommonCropNamesResponse;
import io.swagger.model.core.CommonCropNamesResponseResult;
import io.swagger.model.core.CommonCropNewRequest;
import io.swagger.api.core.CommonCropNamesApi;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
@@ -13,6 +17,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@@ -50,5 +56,48 @@ public class CommonCropNamesApiController extends BrAPIController implements Com
return responseOK(new CommonCropNamesResponse(), new CommonCropNamesResponseResult(), crops, metadata);
}
@CrossOrigin
@Override
public ResponseEntity<CommonCropListResponse> commoncropnamesCropsPost(
@Valid @RequestBody CommonCropNewRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
validateAcceptHeader(request);
validateSecurityContext(request, "ROLE_USER");
log.debug("Request: " + request.getRequestURI());
CommonCrop crop = cropService.createCrop(body.getCommonCropName());
return responseOK(new CommonCropListResponse(), new CommonCropListResponseResult(), List.of(crop));
}
@CrossOrigin
@Override
public ResponseEntity<CommonCropListResponse> commoncropnamesCropsCropDbIdPut(
@PathVariable("cropDbId") String cropDbId,
@Valid @RequestBody CommonCropNewRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
validateAcceptHeader(request);
validateSecurityContext(request, "ROLE_USER");
log.debug("Request: " + request.getRequestURI());
CommonCrop crop = cropService.updateCrop(cropDbId, body.getCommonCropName());
return responseOK(new CommonCropListResponse(), new CommonCropListResponseResult(), List.of(crop));
}
@CrossOrigin
@Override
public ResponseEntity<CommonCropListResponse> commoncropnamesCropsCropDbIdDelete(
@PathVariable("cropDbId") String cropDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
validateAcceptHeader(request);
validateSecurityContext(request, "ROLE_USER");
log.debug("Request: " + request.getRequestURI());
CommonCrop crop = cropService.deleteCrop(cropDbId);
return responseOK(new CommonCropListResponse(), new CommonCropListResponseResult(), List.of(crop));
}
}

View File

@@ -98,6 +98,19 @@ public class LocationsApiController extends BrAPIController implements Locations
return responseOK(new LocationSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<LocationSingleResponse> locationsLocationDbIdDelete(
@PathVariable("locationDbId") String locationDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Location data = locationService.deleteLocation(locationDbId);
return responseOK(new LocationSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<LocationListResponse> locationsPost(@Valid @RequestBody List<LocationNewRequest> body,

View File

@@ -96,6 +96,19 @@ public class PeopleApiController extends BrAPIController implements PeopleApi {
return responseOK(new PersonSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<PersonSingleResponse> peoplePersonDbIdDelete(
@PathVariable("personDbId") String personDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Person data = peopleService.deletePerson(personDbId);
return responseOK(new PersonSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<PersonListResponse> peoplePost(@Valid @RequestBody List<PersonNewRequest> body,

View File

@@ -95,5 +95,18 @@ public class SeasonsApiController extends BrAPIController implements SeasonsApi
return responseOK(new SeasonSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<SeasonSingleResponse> seasonsSeasonDbIdDelete(
@PathVariable("seasonDbId") String seasonDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Season data = seasonService.deleteSeason(seasonDbId);
return responseOK(new SeasonSingleResponse(), data);
}
}

View File

@@ -0,0 +1,198 @@
package org.brapi.test.BrAPITestServer.controller.geno;
import java.util.List;
import org.brapi.test.BrAPITestServer.controller.core.BrAPIController;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceBasesWriteRequest;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceSetWriteRequest;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceWriteRequest;
import org.brapi.test.BrAPITestServer.service.geno.ReferenceBasesService;
import org.brapi.test.BrAPITestServer.service.geno.ReferenceService;
import org.brapi.test.BrAPITestServer.service.geno.ReferenceSetService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.model.Metadata;
import io.swagger.model.geno.Reference;
import io.swagger.model.geno.ReferenceBasesPage;
import io.swagger.model.geno.ReferenceBasesPageListResponse;
import io.swagger.model.geno.ReferenceBasesPageListResponseResult;
import io.swagger.model.geno.ReferenceBasesPageSingleResponse;
import io.swagger.model.geno.ReferenceSet;
import io.swagger.model.geno.ReferenceSetsListResponse;
import io.swagger.model.geno.ReferenceSetsListResponseResult;
import io.swagger.model.geno.ReferenceSetsSingleResponse;
import io.swagger.model.geno.ReferenceSingleResponse;
import io.swagger.model.geno.ReferencesListResponse;
import io.swagger.model.geno.ReferencesListResponseResult;
import jakarta.servlet.http.HttpServletRequest;
@RestController
public class GenotypingReferenceWriteController extends BrAPIController {
private static final Logger log = LoggerFactory.getLogger(GenotypingReferenceWriteController.class);
private final ReferenceSetService referenceSetService;
private final ReferenceService referenceService;
private final ReferenceBasesService referenceBasesService;
private final HttpServletRequest request;
@Autowired
public GenotypingReferenceWriteController(ReferenceSetService referenceSetService, ReferenceService referenceService,
ReferenceBasesService referenceBasesService, HttpServletRequest request) {
this.referenceSetService = referenceSetService;
this.referenceService = referenceService;
this.referenceBasesService = referenceBasesService;
this.request = request;
}
@CrossOrigin
@RequestMapping(value = "/referencesets", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<ReferenceSetsListResponse> referenceSetsPost(@RequestBody ReferenceSetWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceSet data = referenceSetService.saveReferenceSet(body);
return responseOK(new ReferenceSetsListResponse(), new ReferenceSetsListResponseResult(), List.of(data));
}
@CrossOrigin
@RequestMapping(value = "/referencesets/{referenceSetDbId}", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.PUT)
public ResponseEntity<ReferenceSetsSingleResponse> referenceSetsReferenceSetDbIdPut(
@PathVariable("referenceSetDbId") String referenceSetDbId, @RequestBody ReferenceSetWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceSet data = referenceSetService.updateReferenceSet(referenceSetDbId, body);
return responseOK(new ReferenceSetsSingleResponse(), data);
}
@CrossOrigin
@RequestMapping(value = "/referencesets/{referenceSetDbId}", produces = {
"application/json" }, method = RequestMethod.DELETE)
public ResponseEntity<ReferenceSetsSingleResponse> referenceSetsReferenceSetDbIdDelete(
@PathVariable("referenceSetDbId") String referenceSetDbId,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceSet data = referenceSetService.deleteReferenceSet(referenceSetDbId);
return responseOK(new ReferenceSetsSingleResponse(), data);
}
@CrossOrigin
@RequestMapping(value = "/references", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<ReferencesListResponse> referencesPost(@RequestBody ReferenceWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Reference data = referenceService.saveReference(body);
return responseOK(new ReferencesListResponse(), new ReferencesListResponseResult(), List.of(data));
}
@CrossOrigin
@RequestMapping(value = "/references/{referenceDbId}", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.PUT)
public ResponseEntity<ReferenceSingleResponse> referencesReferenceDbIdPut(
@PathVariable("referenceDbId") String referenceDbId, @RequestBody ReferenceWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Reference data = referenceService.updateReference(referenceDbId, body);
return responseOK(new ReferenceSingleResponse(), data);
}
@CrossOrigin
@RequestMapping(value = "/references/{referenceDbId}", produces = {
"application/json" }, method = RequestMethod.DELETE)
public ResponseEntity<ReferenceSingleResponse> referencesReferenceDbIdDelete(
@PathVariable("referenceDbId") String referenceDbId,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Reference data = referenceService.deleteReference(referenceDbId);
return responseOK(new ReferenceSingleResponse(), data);
}
@CrossOrigin
@RequestMapping(value = "/referencebases", produces = { "application/json" }, method = RequestMethod.GET)
public ResponseEntity<ReferenceBasesPageListResponse> referenceBasesGet(
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "pageSize", required = false) Integer pageSize,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_ANONYMOUS", "ROLE_USER");
validateAcceptHeader(request);
Metadata metadata = generateMetaDataTemplate(page, pageSize);
List<ReferenceBasesPage> data = referenceBasesService.findReferenceBasesPages(metadata);
return responseOK(new ReferenceBasesPageListResponse(), new ReferenceBasesPageListResponseResult(), data,
metadata);
}
@CrossOrigin
@RequestMapping(value = "/referencebases", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<ReferenceBasesPageListResponse> referenceBasesPost(@RequestBody ReferenceBasesWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceBasesPage data = referenceBasesService.saveReferenceBasesPage(body);
return responseOK(new ReferenceBasesPageListResponse(), new ReferenceBasesPageListResponseResult(), List.of(data));
}
@CrossOrigin
@RequestMapping(value = "/referencebases/{referenceBasesDbId}", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.PUT)
public ResponseEntity<ReferenceBasesPageSingleResponse> referenceBasesReferenceBasesDbIdPut(
@PathVariable("referenceBasesDbId") String referenceBasesDbId, @RequestBody ReferenceBasesWriteRequest body,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceBasesPage data = referenceBasesService.updateReferenceBasesPage(referenceBasesDbId, body);
return responseOK(new ReferenceBasesPageSingleResponse(), data);
}
@CrossOrigin
@RequestMapping(value = "/referencebases/{referenceBasesDbId}", produces = {
"application/json" }, method = RequestMethod.DELETE)
public ResponseEntity<ReferenceBasesPageSingleResponse> referenceBasesReferenceBasesDbIdDelete(
@PathVariable("referenceBasesDbId") String referenceBasesDbId,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
ReferenceBasesPage data = referenceBasesService.deleteReferenceBasesPage(referenceBasesDbId);
return responseOK(new ReferenceBasesPageSingleResponse(), data);
}
}

View File

@@ -16,8 +16,10 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
@@ -66,5 +68,43 @@ public class BreedingMethodsApiController extends BrAPIController implements Bre
return responseOK(new BreedingMethodSingleResponse(), data);
}
}
@CrossOrigin
@Override
public ResponseEntity<BreedingMethodListResponse> breedingmethodsPost(@Valid @RequestBody List<BreedingMethod> body,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
List<BreedingMethod> data = breedingMethodService.saveBreedingMethods(body);
return responseOK(new BreedingMethodListResponse(), new BreedingMethodListResponseResult(), data);
}
@CrossOrigin
@Override
public ResponseEntity<BreedingMethodSingleResponse> breedingmethodsBreedingMethodDbIdPut(
@PathVariable("breedingMethodDbId") String breedingMethodDbId,
@Valid @RequestBody BreedingMethod body,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
BreedingMethod data = breedingMethodService.updateBreedingMethod(breedingMethodDbId, body);
return responseOK(new BreedingMethodSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<BreedingMethodSingleResponse> breedingmethodsBreedingMethodDbIdDelete(
@PathVariable("breedingMethodDbId") String breedingMethodDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
BreedingMethod data = breedingMethodService.deleteBreedingMethod(breedingMethodDbId);
return responseOK(new BreedingMethodSingleResponse(), data);
}
}

View File

@@ -99,5 +99,18 @@ public class OntologiesApiController extends BrAPIController implements Ontologi
return responseOK(new OntologySingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<OntologySingleResponse> ontologiesOntologyDbIdDelete(
@PathVariable("ontologyDbId") String ontologyDbId,
@RequestHeader(value = "Authorization", required = false) String authorization) throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Ontology data = ontologyService.deleteOntology(ontologyDbId);
return responseOK(new OntologySingleResponse(), data);
}
}

View File

@@ -105,5 +105,18 @@ public class TraitsApiController extends BrAPIController implements TraitsApi {
return responseOK(new TraitSingleResponse(), data);
}
@CrossOrigin
@Override
public ResponseEntity<TraitSingleResponse> traitsTraitDbIdDelete(@PathVariable("traitDbId") String traitDbId,
@RequestHeader(value = "Authorization", required = false) String authorization)
throws BrAPIServerException {
log.debug("Request: " + request.getRequestURI());
validateSecurityContext(request, "ROLE_USER");
validateAcceptHeader(request);
Trait data = traitService.deleteTrait(traitDbId);
return responseOK(new TraitSingleResponse(), data);
}
}

View File

@@ -0,0 +1,40 @@
package org.brapi.test.BrAPITestServer.model.dto.geno;
public class ReferenceBasesWriteRequest {
private String referenceBasesDbId;
private String referenceDbId;
private Integer pageNumber;
private String bases;
public String getReferenceBasesDbId() {
return referenceBasesDbId;
}
public void setReferenceBasesDbId(String referenceBasesDbId) {
this.referenceBasesDbId = referenceBasesDbId;
}
public String getReferenceDbId() {
return referenceDbId;
}
public void setReferenceDbId(String referenceDbId) {
this.referenceDbId = referenceDbId;
}
public Integer getPageNumber() {
return pageNumber;
}
public void setPageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
}
public String getBases() {
return bases;
}
public void setBases(String bases) {
this.bases = bases;
}
}

View File

@@ -0,0 +1,87 @@
package org.brapi.test.BrAPITestServer.model.dto.geno;
import io.swagger.model.geno.OntologyTerm;
public class ReferenceSetWriteRequest {
private String referenceSetDbId;
private String referenceSetName;
private String assemblyPUI;
private String description;
private Boolean isDerived;
private String md5checksum;
private String sourceURI;
private OntologyTerm species;
private String sourceGermplasmDbId;
public String getReferenceSetDbId() {
return referenceSetDbId;
}
public void setReferenceSetDbId(String referenceSetDbId) {
this.referenceSetDbId = referenceSetDbId;
}
public String getReferenceSetName() {
return referenceSetName;
}
public void setReferenceSetName(String referenceSetName) {
this.referenceSetName = referenceSetName;
}
public String getAssemblyPUI() {
return assemblyPUI;
}
public void setAssemblyPUI(String assemblyPUI) {
this.assemblyPUI = assemblyPUI;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getIsDerived() {
return isDerived;
}
public void setIsDerived(Boolean isDerived) {
this.isDerived = isDerived;
}
public String getMd5checksum() {
return md5checksum;
}
public void setMd5checksum(String md5checksum) {
this.md5checksum = md5checksum;
}
public String getSourceURI() {
return sourceURI;
}
public void setSourceURI(String sourceURI) {
this.sourceURI = sourceURI;
}
public OntologyTerm getSpecies() {
return species;
}
public void setSpecies(OntologyTerm species) {
this.species = species;
}
public String getSourceGermplasmDbId() {
return sourceGermplasmDbId;
}
public void setSourceGermplasmDbId(String sourceGermplasmDbId) {
this.sourceGermplasmDbId = sourceGermplasmDbId;
}
}

View File

@@ -0,0 +1,58 @@
package org.brapi.test.BrAPITestServer.model.dto.geno;
public class ReferenceWriteRequest {
private String referenceDbId;
private String referenceName;
private String referenceSetDbId;
private Integer length;
private String md5checksum;
private Float sourceDivergence;
public String getReferenceDbId() {
return referenceDbId;
}
public void setReferenceDbId(String referenceDbId) {
this.referenceDbId = referenceDbId;
}
public String getReferenceName() {
return referenceName;
}
public void setReferenceName(String referenceName) {
this.referenceName = referenceName;
}
public String getReferenceSetDbId() {
return referenceSetDbId;
}
public void setReferenceSetDbId(String referenceSetDbId) {
this.referenceSetDbId = referenceSetDbId;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public String getMd5checksum() {
return md5checksum;
}
public void setMd5checksum(String md5checksum) {
this.md5checksum = md5checksum;
}
public Float getSourceDivergence() {
return sourceDivergence;
}
public void setSourceDivergence(Float sourceDivergence) {
this.sourceDivergence = sourceDivergence;
}
}

View File

@@ -27,21 +27,29 @@ public class PersonEntity extends BrAPIPrimaryEntity {
@Column
private String instituteName;
public String getName() {
String name = getFirstName();
if (!getLastName().isEmpty())
name = (name + " " + getLastName()).trim();
private static String safeText(String value) {
return value == null ? "" : value;
}
public String getName() {
String name = safeText(getFirstName());
String lastName = safeText(getLastName());
if (!lastName.isEmpty()) {
name = (name + " " + lastName).trim();
}
return name;
}
public String getFullName() {
String name = getFirstName();
if (!getMiddleName().isEmpty())
name = (name + " " + getMiddleName()).trim();
if (!getLastName().isEmpty())
name = (name + " " + getLastName()).trim();
String name = safeText(getFirstName());
String middleName = safeText(getMiddleName());
String lastName = safeText(getLastName());
if (!middleName.isEmpty()) {
name = (name + " " + middleName).trim();
}
if (!lastName.isEmpty()) {
name = (name + " " + lastName).trim();
}
return name;
}

View File

@@ -24,7 +24,7 @@ public class TrialEntity extends BrAPIPrimaryEntity {
private String documentationURL;
@Column
private Date endDate;
@OneToMany(mappedBy = "trial")
@OneToMany(mappedBy = "trial", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PublicationEntity> publications;
@Column
private Date startDate;

View File

@@ -32,4 +32,12 @@ public class ReferenceBasesPageEntity extends BrAPIPrimaryEntity {
this.bases = bases;
}
public Integer getPageNumber() {
return pageNumber;
}
public void setPageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
}
}

View File

@@ -42,11 +42,13 @@ public class BrAPIRepositoryImpl<T extends BrAPIPrimaryEntity, ID extends Serial
public Optional<T> findById(ID id) {
Optional<T> response = super.findById(id);
if (response.isPresent()) {
if (response.isPresent() && !hasAdminRole()) {
String userId = getCurrentUserId();
if (!(null == response.get().getAuthUserId()
|| userId.equals(response.get().getAuthUserId())
|| "anonymousUser".equals(response.get().getAuthUserId()))) {
String authUserId = response.get().getAuthUserId();
if (!(authUserId == null
|| authUserId.isBlank()
|| userId.equals(authUserId)
|| "anonymousUser".equals(authUserId))) {
response = Optional.empty();
}
}
@@ -72,12 +74,22 @@ public class BrAPIRepositoryImpl<T extends BrAPIPrimaryEntity, ID extends Serial
private String getCurrentUserId() {
SecurityContext context = SecurityContextHolder.getContext();
String userId = "";
if (context.getAuthentication().getPrincipal() != null) {
if (context.getAuthentication() != null && context.getAuthentication().getPrincipal() != null) {
userId = context.getAuthentication().getPrincipal().toString();
}
return userId;
}
private boolean hasAdminRole() {
SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() == null) {
return false;
}
return context.getAuthentication().getAuthorities().stream()
.map(auth -> auth.getAuthority())
.anyMatch("ROLE_ADMIN"::equals);
}
private SearchQueryBuilder<T> applyUserId(SearchQueryBuilder<T> searchQuery) {
SecurityContext context = SecurityContextHolder.getContext();

View File

@@ -7,4 +7,5 @@ import org.springframework.data.domain.Pageable;
public interface ReferenceBaseRepository extends BrAPIRepository<ReferenceBasesPageEntity, String> {
public Page<ReferenceBasesPageEntity> findByReferenceIdAndPageNumber(String referenceId, int pageNumber, Pageable pageReq);
public Page<ReferenceBasesPageEntity> findByReferenceId(String referenceId, Pageable pageReq);
}

View File

@@ -5,4 +5,6 @@ import org.brapi.test.BrAPITestServer.repository.BrAPIRepository;
public interface GermplasmRepository extends BrAPIRepository<GermplasmEntity, String> {
long countByBreedingMethod_Id(String breedingMethodId);
}

View File

@@ -1,6 +1,7 @@
package org.brapi.test.BrAPITestServer.service.core;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
@@ -8,12 +9,14 @@ import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.entity.core.CropEntity;
import org.brapi.test.BrAPITestServer.repository.core.CropRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import io.swagger.model.Metadata;
import io.swagger.model.core.CommonCrop;
@Service
public class CropService {
@@ -73,4 +76,58 @@ public class CropService {
return entity;
}
public CommonCrop createCrop(String commonCropName) throws BrAPIServerException {
String name = validateCropName(commonCropName);
if (findCropEntity(name) != null) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Crop already exists: " + name);
}
return convertToCommonCrop(saveCropEntity(name));
}
public CommonCrop updateCrop(String cropDbId, String commonCropName) throws BrAPIServerException {
String name = validateCropName(commonCropName);
CropEntity entity = resolveCropEntity(cropDbId);
if (!name.equals(entity.getCropName()) && findCropEntity(name) != null) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Crop already exists: " + name);
}
entity.setCropName(name);
return convertToCommonCrop(cropRepository.saveAndFlush(entity));
}
public CommonCrop deleteCrop(String cropDbId) throws BrAPIServerException {
CropEntity entity = resolveCropEntity(cropDbId);
CommonCrop deleted = convertToCommonCrop(entity);
try {
cropRepository.delete(entity);
cropRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Crop is in use and cannot be deleted");
}
return deleted;
}
private CropEntity resolveCropEntity(String cropDbId) throws BrAPIServerException {
Optional<CropEntity> byId = cropRepository.findById(cropDbId);
if (byId.isPresent()) {
return byId.get();
}
return getCropEntity(cropDbId);
}
private String validateCropName(String commonCropName) throws BrAPIServerException {
if (commonCropName == null || commonCropName.isBlank()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "commonCropName is required");
}
return commonCropName.trim();
}
public CommonCrop convertToCommonCrop(CropEntity entity) {
CommonCrop crop = new CommonCrop();
if (entity != null) {
crop.setCropDbId(entity.getCropName());
crop.setCommonCropName(entity.getCropName());
}
return crop;
}
}

View File

@@ -15,6 +15,7 @@ import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -118,6 +119,18 @@ public class LocationService {
return convertFromEntity(savedEntity);
}
public Location deleteLocation(String locationDbId) throws BrAPIServerException {
LocationEntity entity = getLocationEntity(locationDbId, HttpStatus.NOT_FOUND);
Location deleted = convertFromEntity(entity);
try {
locationRepository.delete(entity);
locationRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Location is in use and cannot be deleted");
}
return deleted;
}
public List<Location> saveLocations(@Valid List<LocationNewRequest> body) throws BrAPIServerException {
List<Location> savedLocations = new ArrayList<>();

View File

@@ -10,6 +10,7 @@ import org.brapi.test.BrAPITestServer.model.entity.core.PersonEntity;
import org.brapi.test.BrAPITestServer.repository.core.PeopleRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -101,6 +102,18 @@ public class PeopleService {
return convertToPerson(savedEntity);
}
public Person deletePerson(String personDbId) throws BrAPIServerException {
PersonEntity entity = getPersonEntity(personDbId, HttpStatus.NOT_FOUND);
Person deleted = convertToPerson(entity);
try {
peopleRepository.delete(entity);
peopleRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Person is in use and cannot be deleted");
}
return deleted;
}
public List<Person> savePeople(List<PersonNewRequest> body) {
List<Person> savedPeople = new ArrayList<>();

View File

@@ -13,6 +13,7 @@ import org.brapi.test.BrAPITestServer.repository.core.SeasonRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -79,6 +80,18 @@ public class SeasonService {
return convertFromEntity(savedEntity);
}
public Season deleteSeason(String seasonDbId) throws BrAPIServerException {
SeasonEntity entity = getSeasonEntity(seasonDbId, HttpStatus.NOT_FOUND);
Season deleted = convertFromEntity(entity);
try {
seasonRepository.delete(entity);
seasonRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Season is in use and cannot be deleted");
}
return deleted;
}
public List<Season> saveSeasons(@Valid List<Season> body) {
List<Season> savedSeasons = new ArrayList<>();

View File

@@ -152,7 +152,7 @@ public class StudyService {
Page<StudyEntity> studiesPage = studyRepository.findAllBySearch(searchQuery, pageReq);
PagingUtility.calculateMetaData(metaData, studiesPage);
List<Study> studies = studiesPage.map(this::convertFromEntity).getContent();
List<Study> studies = studiesPage.map(this::convertFromEntityForList).getContent();
return studies;
}
@@ -301,61 +301,18 @@ public class StudyService {
}
}
private Study convertFromEntity(StudyEntity entity) {
/** List/search responses: avoid lazy-loading heavy child collections (dataLinks, variables, etc.). */
private Study convertFromEntityForList(StudyEntity entity) {
Study study = new Study();
study.setActive(entity.isActive());
study.setAdditionalInfo(entity.getAdditionalInfoMap());
if (entity.getContacts() != null) {
study.setContacts(entity.getContacts().stream().map(this.peopleService::convertToContact)
.collect(Collectors.toList()));
}
study.setExternalReferences(entity.getExternalReferencesMap());
study.setCulturalPractices(entity.getCulturalPractices());
if (entity.getDataLinks() != null) {
study.setDataLinks(
entity.getDataLinks().stream().map(this::convertFromEntity).collect(Collectors.toList()));
}
study.setDocumentationURL(entity.getDocumentationURL());
study.setEndDate(DateUtility.toOffsetDateTime(entity.getEndDate()));
if (entity.getEnvironmentParameters() != null) {
study.setEnvironmentParameters(entity.getEnvironmentParameters().stream().map(this::convertFromEntity)
.collect(Collectors.toList()));
}
study.setExperimentalDesign(convertFromEntity(entity.getExperimentalDesign()));
study.setExternalReferences(entity.getExternalReferencesMap());
study.setGrowthFacility(convertFromEntity(entity.getGrowthFacility()));
study.setLastUpdate(convertFromEntity(entity.getLastUpdate()));
study.setLicense(entity.getLicense());
if (entity.getLocation() != null) {
study.setLocationDbId(entity.getLocation().getId());
study.setLocationName(entity.getLocation().getLocationName());
}
if (entity.getObservationLevels() != null) {
study.setObservationLevels(
entity.getObservationLevels().stream().map(this::convertFromEntity).collect(Collectors.toList()));
}
study.setObservationUnitsDescription(entity.getObservationUnitsDescription());
if (entity.getObservationVariables() != null) {
study.setObservationVariableDbIds(entity.getObservationVariables().stream().map(e -> {
return e.getId();
}).collect(Collectors.toList()));
}
if (entity.getSeasons() != null) {
study.setSeasons(entity.getSeasons().stream().map(e -> {
return e.getId();
}).collect(Collectors.toList()));
}
study.setStartDate(DateUtility.toOffsetDateTime(entity.getStartDate()));
study.setStudyCode(entity.getStudyCode());
study.setStudyDbId(entity.getId());
@@ -364,20 +321,27 @@ public class StudyService {
study.setStudyPUI(entity.getStudyPUI());
study.setStudyType(entity.getStudyType());
if (entity.getContacts() != null) {
study.setContacts(entity.getContacts().stream().map(this.peopleService::convertToContact)
.collect(Collectors.toList()));
}
if (entity.getSeasons() != null) {
study.setSeasons(entity.getSeasons().stream().map(SeasonEntity::getId).collect(Collectors.toList()));
}
if (entity.getLocation() != null) {
study.setLocationDbId(entity.getLocation().getId());
study.setLocationName(entity.getLocation().getLocationName());
}
if (entity.getTrial() != null) {
study.setTrialDbId(entity.getTrial().getId());
study.setTrialName(entity.getTrial().getTrialName());
if (entity.getTrial().getProgram() != null) {
if (entity.getTrial().getProgram().getCrop() != null) {
study.setCommonCropName(entity.getTrial().getProgram().getCrop().getCropName());
}
if (entity.getTrial().getProgram() != null && entity.getTrial().getProgram().getCrop() != null) {
study.setCommonCropName(entity.getTrial().getProgram().getCrop().getCropName());
}
}
if (entity.getProgram() != null) {
if (entity.getProgram().getCrop() != null) {
study.setCommonCropName(entity.getProgram().getCrop().getCropName());
}
}
}
if (entity.getProgram() != null && entity.getProgram().getCrop() != null) {
study.setCommonCropName(entity.getProgram().getCrop().getCropName());
}
if (entity.getCrop() != null) {
study.setCommonCropName(entity.getCrop().getCropName());
}
@@ -385,6 +349,37 @@ public class StudyService {
return study;
}
private Study convertFromEntity(StudyEntity entity) {
Study study = convertFromEntityForList(entity);
if (entity.getDataLinks() != null) {
study.setDataLinks(
entity.getDataLinks().stream().map(this::convertFromEntity).collect(Collectors.toList()));
}
if (entity.getEnvironmentParameters() != null) {
study.setEnvironmentParameters(entity.getEnvironmentParameters().stream().map(this::convertFromEntity)
.collect(Collectors.toList()));
}
study.setExperimentalDesign(convertFromEntity(entity.getExperimentalDesign()));
study.setGrowthFacility(convertFromEntity(entity.getGrowthFacility()));
study.setLastUpdate(convertFromEntity(entity.getLastUpdate()));
if (entity.getObservationLevels() != null) {
study.setObservationLevels(
entity.getObservationLevels().stream().map(this::convertFromEntity).collect(Collectors.toList()));
}
if (entity.getObservationVariables() != null) {
study.setObservationVariableDbIds(entity.getObservationVariables().stream().map(e -> {
return e.getId();
}).collect(Collectors.toList()));
}
return study;
}
private ObservationUnitHierarchyLevel convertFromEntity(ObservationLevelEntity entity) {
ObservationUnitHierarchyLevel level = null;
if (entity != null) {

View File

@@ -240,13 +240,17 @@ public class TrialService {
if (body.getExternalReferences() != null)
entity.setExternalReferences(body.getExternalReferences());
if (body.getPublications() != null) {
if (entity.getPublications() != null) {
for (PublicationEntity pub : entity.getPublications()) {
pub.setTrial(null);
if (entity.getPublications() == null) {
entity.setPublications(new ArrayList<>());
}
entity.getPublications().clear();
for (TrialNewRequestPublications pub : body.getPublications()) {
PublicationEntity publicationEntity = convertToEntity(pub);
if (publicationEntity != null) {
publicationEntity.setTrial(entity);
entity.getPublications().add(publicationEntity);
}
}
entity.setPublications(
body.getPublications().stream().map(this::convertToEntity).collect(Collectors.toList()));
}
if (body.getStartDate() != null)
entity.setStartDate(DateUtility.toDate(body.getStartDate()));

View File

@@ -0,0 +1,153 @@
package org.brapi.test.BrAPITestServer.service.geno;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceBasesWriteRequest;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceBasesPageEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceEntity;
import org.brapi.test.BrAPITestServer.repository.geno.ReferenceBaseRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import io.swagger.model.Metadata;
import io.swagger.model.geno.ReferenceBasesPage;
@Service
public class ReferenceBasesService {
private static final Pattern BASES_PATTERN = Pattern.compile("^[ACGTNacgtn*.-]*$");
private final ReferenceBaseRepository referenceBaseRepository;
private final ReferenceService referenceService;
public ReferenceBasesService(ReferenceBaseRepository referenceBaseRepository, ReferenceService referenceService) {
this.referenceBaseRepository = referenceBaseRepository;
this.referenceService = referenceService;
}
public List<ReferenceBasesPage> findReferenceBasesPages(Metadata metadata) {
Pageable pageReq = PagingUtility.getPageRequest(metadata);
Page<ReferenceBasesPageEntity> page = referenceBaseRepository.findAll(pageReq);
List<ReferenceBasesPage> data = page.getContent().stream().map(this::convertFromEntity).collect(Collectors.toList());
PagingUtility.calculateMetaData(metadata, page);
return data;
}
public ReferenceBasesPage getReferenceBasesPage(String referenceBasesDbId) throws BrAPIServerException {
return convertFromEntity(getReferenceBasesPageEntity(referenceBasesDbId, HttpStatus.NOT_FOUND));
}
public ReferenceBasesPage saveReferenceBasesPage(ReferenceBasesWriteRequest request) throws BrAPIServerException {
validateRequest(request, true);
if (request.getReferenceBasesDbId() != null
&& referenceBaseRepository.findById(request.getReferenceBasesDbId()).isPresent()) {
throw new BrAPIServerException(HttpStatus.CONFLICT,
"ReferenceBases already exists: " + request.getReferenceBasesDbId());
}
assertUniquePageNumber(null, request.getReferenceDbId(), request.getPageNumber());
ReferenceBasesPageEntity entity = new ReferenceBasesPageEntity();
if (request.getReferenceBasesDbId() != null && !request.getReferenceBasesDbId().isBlank()) {
entity.setId(request.getReferenceBasesDbId().trim());
}
updateEntity(entity, request);
return convertFromEntity(referenceBaseRepository.save(entity));
}
public ReferenceBasesPage updateReferenceBasesPage(String referenceBasesDbId, ReferenceBasesWriteRequest request)
throws BrAPIServerException {
validateRequest(request, false);
ReferenceBasesPageEntity entity = getReferenceBasesPageEntity(referenceBasesDbId, HttpStatus.NOT_FOUND);
assertUniquePageNumber(referenceBasesDbId, request.getReferenceDbId(), request.getPageNumber());
updateEntity(entity, request);
return convertFromEntity(referenceBaseRepository.save(entity));
}
public ReferenceBasesPage deleteReferenceBasesPage(String referenceBasesDbId) throws BrAPIServerException {
ReferenceBasesPageEntity entity = getReferenceBasesPageEntity(referenceBasesDbId, HttpStatus.NOT_FOUND);
ReferenceBasesPage deleted = convertFromEntity(entity);
try {
referenceBaseRepository.delete(entity);
referenceBaseRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceBases cannot be deleted");
}
return deleted;
}
private ReferenceBasesPageEntity getReferenceBasesPageEntity(String referenceBasesDbId, HttpStatus errorStatus)
throws BrAPIServerException {
Optional<ReferenceBasesPageEntity> entityOpt = referenceBaseRepository.findById(referenceBasesDbId);
if (entityOpt.isPresent()) {
return entityOpt.get();
}
throw new BrAPIServerDbIdNotFoundException("referenceBases", referenceBasesDbId, errorStatus);
}
private void validateRequest(ReferenceBasesWriteRequest request, boolean creating) throws BrAPIServerException {
if (request.getReferenceDbId() == null || request.getReferenceDbId().isBlank()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "referenceDbId is required");
}
if (request.getPageNumber() == null) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "pageNumber is required");
}
if (request.getPageNumber() < 0) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "pageNumber must be non-negative");
}
if (creating && (request.getBases() == null || request.getBases().isBlank())) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "bases is required");
}
if (request.getBases() != null) {
if (request.getBases().length() > 2048) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "bases cannot exceed 2048 characters");
}
if (!BASES_PATTERN.matcher(request.getBases()).matches()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "bases contains invalid characters");
}
}
}
private void assertUniquePageNumber(String currentId, String referenceDbId, Integer pageNumber)
throws BrAPIServerException {
Pageable pageReq = PageRequest.of(0, 1000, Sort.by("pageNumber"));
Page<ReferenceBasesPageEntity> page = referenceBaseRepository.findByReferenceId(referenceDbId, pageReq);
for (ReferenceBasesPageEntity existing : page.getContent()) {
if (pageNumber != null && pageNumber.equals(existing.getPageNumber())
&& (currentId == null || !currentId.equals(existing.getId()))) {
throw new BrAPIServerException(HttpStatus.CONFLICT,
"pageNumber already exists for this reference: " + pageNumber);
}
}
}
private void updateEntity(ReferenceBasesPageEntity entity, ReferenceBasesWriteRequest request)
throws BrAPIServerException {
ReferenceEntity reference = referenceService.getReferenceEntity(request.getReferenceDbId());
entity.setReference(reference);
entity.setPageNumber(request.getPageNumber());
if (request.getBases() != null) {
entity.setBases(request.getBases().trim().toUpperCase());
}
}
private ReferenceBasesPage convertFromEntity(ReferenceBasesPageEntity entity) {
ReferenceBasesPage page = new ReferenceBasesPage();
page.setReferenceBasesDbId(entity.getId());
page.setPageNumber(entity.getPageNumber());
page.setBases(entity.getBases());
if (entity.getReference() != null) {
page.setReferenceDbId(entity.getReference().getId());
page.setReferenceName(entity.getReference().getReferenceName());
}
return page;
}
}

View File

@@ -1,5 +1,6 @@
package org.brapi.test.BrAPITestServer.service.geno;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -9,6 +10,7 @@ import jakarta.validation.Valid;
import org.apache.commons.lang3.math.NumberUtils;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceWriteRequest;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceBasesPageEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceSetEntity;
@@ -17,6 +19,7 @@ import org.brapi.test.BrAPITestServer.repository.geno.ReferenceRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -38,10 +41,13 @@ public class ReferenceService {
private final ReferenceRepository referenceRepository;
private final ReferenceBaseRepository referenceBaseRepository;
private final ReferenceSetService referenceSetService;
public ReferenceService(ReferenceRepository referenceRepository, ReferenceBaseRepository referenceBaseRepository) {
public ReferenceService(ReferenceRepository referenceRepository, ReferenceBaseRepository referenceBaseRepository,
ReferenceSetService referenceSetService) {
this.referenceRepository = referenceRepository;
this.referenceBaseRepository = referenceBaseRepository;
this.referenceSetService = referenceSetService;
}
public List<Reference> findReferences(String referenceDbId, String referenceSetDbId, String accession,
@@ -121,7 +127,7 @@ public class ReferenceService {
if (entity.getReferenceSet() != null) {
ReferenceSetEntity refSetEntity = entity.getReferenceSet();
ref.setReferenceSetDbId(refSetEntity.getId());
ref.setReferenceName(refSetEntity.getReferenceSetName());
ref.setReferenceSetName(refSetEntity.getReferenceSetName());
ref.setIsDerived(refSetEntity.getIsDerived());
ref.setSourceURI(entity.getReferenceSet().getSourceURI());
OntologyTerm term = new OntologyTerm().term(entity.getReferenceSet().getSpeciesOntologyTerm())
@@ -226,4 +232,56 @@ public class ReferenceService {
return bases;
}
public Reference saveReference(ReferenceWriteRequest request) throws BrAPIServerException {
if (request.getReferenceName() == null || request.getReferenceName().isBlank()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "referenceName is required");
}
if (request.getReferenceSetDbId() == null || request.getReferenceSetDbId().isBlank()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "referenceSetDbId is required");
}
if (request.getReferenceDbId() != null && referenceRepository.findById(request.getReferenceDbId()).isPresent()) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Reference already exists: " + request.getReferenceDbId());
}
ReferenceEntity entity = new ReferenceEntity();
if (request.getReferenceDbId() != null && !request.getReferenceDbId().isBlank()) {
entity.setId(request.getReferenceDbId().trim());
}
updateEntity(entity, request);
return convertFromEntity(referenceRepository.save(entity));
}
public Reference updateReference(String referenceDbId, ReferenceWriteRequest request) throws BrAPIServerException {
ReferenceEntity entity = getReferenceEntity(referenceDbId, HttpStatus.NOT_FOUND);
updateEntity(entity, request);
return convertFromEntity(referenceRepository.save(entity));
}
public Reference deleteReference(String referenceDbId) throws BrAPIServerException {
ReferenceEntity entity = getReferenceEntity(referenceDbId, HttpStatus.NOT_FOUND);
Pageable pageReq = PageRequest.of(0, 1);
Page<ReferenceBasesPageEntity> basesPage = referenceBaseRepository.findByReferenceId(referenceDbId, pageReq);
if (basesPage.getTotalElements() > 0) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Reference is referenced by ReferenceBases records");
}
Reference deleted = convertFromEntity(entity);
try {
referenceRepository.delete(entity);
referenceRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Reference is in use and cannot be deleted");
}
return deleted;
}
private void updateEntity(ReferenceEntity entity, ReferenceWriteRequest request) throws BrAPIServerException {
entity.setReferenceName(request.getReferenceName().trim());
ReferenceSetEntity referenceSet = referenceSetService.getReferenceSetEntity(request.getReferenceSetDbId());
entity.setReferenceSet(referenceSet);
entity.setLength(request.getLength());
entity.setMd5checksum(request.getMd5checksum());
if (request.getSourceDivergence() != null) {
entity.setSourceDivergence(BigDecimal.valueOf(request.getSourceDivergence()));
}
}
}

View File

@@ -6,12 +6,23 @@ import java.util.Optional;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceSetWriteRequest;
import org.brapi.test.BrAPITestServer.model.entity.germ.GermplasmEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceSetEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.VariantEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.VariantSetEntity;
import org.brapi.test.BrAPITestServer.repository.geno.ReferenceRepository;
import org.brapi.test.BrAPITestServer.repository.geno.ReferenceSetRepository;
import org.brapi.test.BrAPITestServer.repository.geno.VariantRepository;
import org.brapi.test.BrAPITestServer.repository.geno.VariantSetRepository;
import org.brapi.test.BrAPITestServer.service.germ.GermplasmService;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@@ -26,9 +37,19 @@ import io.swagger.model.geno.ReferenceSourceGermplasm;
public class ReferenceSetService {
private final ReferenceSetRepository referenceSetRepository;
private final ReferenceRepository referenceRepository;
private final VariantSetRepository variantSetRepository;
private final VariantRepository variantRepository;
private final GermplasmService germplasmService;
public ReferenceSetService(ReferenceSetRepository referenceSetRepository) {
public ReferenceSetService(ReferenceSetRepository referenceSetRepository, ReferenceRepository referenceRepository,
VariantSetRepository variantSetRepository, VariantRepository variantRepository,
GermplasmService germplasmService) {
this.referenceSetRepository = referenceSetRepository;
this.referenceRepository = referenceRepository;
this.variantSetRepository = variantSetRepository;
this.variantRepository = variantRepository;
this.germplasmService = germplasmService;
}
public List<ReferenceSet> findReferenceSets(String referenceSetDbId, String accession, String assemblyPUI,
@@ -124,4 +145,77 @@ public class ReferenceSetService {
return refSet;
}
public ReferenceSet saveReferenceSet(ReferenceSetWriteRequest request) throws BrAPIServerException {
if (request.getReferenceSetName() == null || request.getReferenceSetName().isBlank()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "referenceSetName is required");
}
if (request.getReferenceSetDbId() != null && referenceSetRepository.findById(request.getReferenceSetDbId()).isPresent()) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet already exists: " + request.getReferenceSetDbId());
}
ReferenceSetEntity entity = new ReferenceSetEntity();
if (request.getReferenceSetDbId() != null && !request.getReferenceSetDbId().isBlank()) {
entity.setId(request.getReferenceSetDbId().trim());
}
updateEntity(entity, request);
return convertFromEntity(referenceSetRepository.save(entity));
}
public ReferenceSet updateReferenceSet(String referenceSetDbId, ReferenceSetWriteRequest request)
throws BrAPIServerException {
ReferenceSetEntity entity = getReferenceSetEntity(referenceSetDbId, HttpStatus.NOT_FOUND);
updateEntity(entity, request);
return convertFromEntity(referenceSetRepository.save(entity));
}
public ReferenceSet deleteReferenceSet(String referenceSetDbId) throws BrAPIServerException {
ReferenceSetEntity entity = getReferenceSetEntity(referenceSetDbId, HttpStatus.NOT_FOUND);
assertNoReferenceSetDependencies(referenceSetDbId);
ReferenceSet deleted = convertFromEntity(entity);
try {
referenceSetRepository.delete(entity);
referenceSetRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is in use and cannot be deleted");
}
return deleted;
}
private void assertNoReferenceSetDependencies(String referenceSetDbId) throws BrAPIServerException {
Pageable pageReq = PageRequest.of(0, 1);
SearchQueryBuilder<ReferenceEntity> referenceQuery = new SearchQueryBuilder<ReferenceEntity>(ReferenceEntity.class)
.appendSingle(referenceSetDbId, "referenceSet.id");
if (referenceRepository.findAllBySearch(referenceQuery, pageReq).getTotalElements() > 0) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by Reference records");
}
SearchQueryBuilder<VariantSetEntity> variantSetQuery = new SearchQueryBuilder<VariantSetEntity>(VariantSetEntity.class)
.appendSingle(referenceSetDbId, "referenceSet.id");
if (variantSetRepository.findAllBySearch(variantSetQuery, pageReq).getTotalElements() > 0) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by VariantSet records");
}
SearchQueryBuilder<VariantEntity> variantQuery = new SearchQueryBuilder<VariantEntity>(VariantEntity.class)
.appendSingle(referenceSetDbId, "referenceSet.id");
if (variantRepository.findAllBySearch(variantQuery, pageReq).getTotalElements() > 0) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by Variant records");
}
}
private void updateEntity(ReferenceSetEntity entity, ReferenceSetWriteRequest request) throws BrAPIServerException {
entity.setReferenceSetName(request.getReferenceSetName().trim());
entity.setAssemblyPUI(request.getAssemblyPUI());
entity.setDescription(request.getDescription());
entity.setIsDerived(request.getIsDerived());
entity.setMd5checksum(request.getMd5checksum());
entity.setSourceURI(request.getSourceURI());
if (request.getSpecies() != null) {
entity.setSpeciesOntologyTerm(request.getSpecies().getTerm());
entity.setSpeciesOntologyTermURI(request.getSpecies().getTermURI());
}
if (request.getSourceGermplasmDbId() != null && !request.getSourceGermplasmDbId().isBlank()) {
GermplasmEntity germplasm = germplasmService.getGermplasmEntity(request.getSourceGermplasmDbId());
entity.setSourceGermplasm(germplasm);
} else if (request.getSourceGermplasmDbId() != null) {
entity.setSourceGermplasm(null);
}
}
}

View File

@@ -1,12 +1,16 @@
package org.brapi.test.BrAPITestServer.service.germ;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import jakarta.validation.Valid;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.entity.germ.BreedingMethodEntity;
import org.brapi.test.BrAPITestServer.repository.germ.BreedingMethodRepository;
import org.brapi.test.BrAPITestServer.repository.germ.GermplasmRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -20,9 +24,12 @@ import io.swagger.model.germ.BreedingMethod;
public class BreedingMethodService {
private final BreedingMethodRepository breedingMethodRepository;
private final GermplasmRepository germplasmRepository;
public BreedingMethodService(BreedingMethodRepository breedingMethodRepository) {
public BreedingMethodService(BreedingMethodRepository breedingMethodRepository,
GermplasmRepository germplasmRepository) {
this.breedingMethodRepository = breedingMethodRepository;
this.germplasmRepository = germplasmRepository;
}
public List<BreedingMethod> findBreedingMethods(Metadata metadata) {
@@ -52,6 +59,50 @@ public class BreedingMethodService {
return breedingMethodEntity;
}
public List<BreedingMethod> saveBreedingMethods(@Valid List<BreedingMethod> body) throws BrAPIServerException {
List<BreedingMethod> savedMethods = new ArrayList<>();
for (BreedingMethod method : body) {
validateBreedingMethodName(method.getBreedingMethodName());
BreedingMethodEntity entity = new BreedingMethodEntity();
updateEntity(entity, method);
BreedingMethodEntity savedEntity = breedingMethodRepository.save(entity);
savedMethods.add(convertFromEntity(savedEntity));
}
return savedMethods;
}
public BreedingMethod updateBreedingMethod(String breedingMethodDbId, BreedingMethod body) throws BrAPIServerException {
BreedingMethodEntity entity = getBreedingMethodEntity(breedingMethodDbId, HttpStatus.NOT_FOUND);
updateEntity(entity, body);
BreedingMethodEntity savedEntity = breedingMethodRepository.save(entity);
return convertFromEntity(savedEntity);
}
public BreedingMethod deleteBreedingMethod(String breedingMethodDbId) throws BrAPIServerException {
BreedingMethodEntity entity = getBreedingMethodEntity(breedingMethodDbId, HttpStatus.NOT_FOUND);
long usageCount = germplasmRepository.countByBreedingMethod_Id(breedingMethodDbId);
if (usageCount > 0) {
throw new BrAPIServerException(HttpStatus.CONFLICT,
"Breeding method is in use by germplasm and cannot be deleted");
}
BreedingMethod deleted = convertFromEntity(entity);
breedingMethodRepository.delete(entity);
breedingMethodRepository.flush();
return deleted;
}
public long countGermplasmUsage(String breedingMethodDbId) {
return germplasmRepository.countByBreedingMethod_Id(breedingMethodDbId);
}
private void validateBreedingMethodName(String breedingMethodName) throws BrAPIServerException {
if (breedingMethodName == null || breedingMethodName.trim().isEmpty()) {
throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "breedingMethodName is required");
}
}
private BreedingMethod convertFromEntity(BreedingMethodEntity entity) {
BreedingMethod method = new BreedingMethod();
method.setAbbreviation(entity.getAbbreviation());
@@ -61,4 +112,17 @@ public class BreedingMethodService {
return method;
}
private void updateEntity(BreedingMethodEntity entity, BreedingMethod request) throws BrAPIServerException {
if (request.getBreedingMethodName() != null) {
validateBreedingMethodName(request.getBreedingMethodName());
entity.setName(request.getBreedingMethodName().trim());
}
if (request.getAbbreviation() != null) {
entity.setAbbreviation(request.getAbbreviation().trim().isEmpty() ? null : request.getAbbreviation().trim());
}
if (request.getDescription() != null) {
entity.setDescription(request.getDescription().trim().isEmpty() ? null : request.getDescription().trim());
}
}
}

View File

@@ -13,6 +13,7 @@ import org.brapi.test.BrAPITestServer.repository.pheno.OntologyRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -76,6 +77,18 @@ public class OntologyService {
return convertFromEntity(savedEntity);
}
public Ontology deleteOntology(String ontologyDbId) throws BrAPIServerException {
OntologyEntity entity = getOntologyEntity(ontologyDbId, HttpStatus.NOT_FOUND);
Ontology deleted = convertFromEntity(entity);
try {
ontologyRepository.delete(entity);
ontologyRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Ontology is in use and cannot be deleted");
}
return deleted;
}
public Ontology convertFromEntity(OntologyEntity entity) {
Ontology ontology = new Ontology();
UpdateUtility.convertFromEntity(entity, ontology);

View File

@@ -13,6 +13,7 @@ import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -59,6 +60,18 @@ public class TraitService {
return convertFromEntity(savedEntity);
}
public Trait deleteTrait(String traitDbId) throws BrAPIServerException {
TraitEntity entity = getTraitEntity(traitDbId, HttpStatus.NOT_FOUND);
Trait deleted = convertFromEntity(entity);
try {
traitRepository.delete(entity);
traitRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BrAPIServerException(HttpStatus.CONFLICT, "Trait is in use and cannot be deleted");
}
return deleted;
}
public List<Trait> saveTraits(@Valid List<TraitBaseClass> body) throws BrAPIServerException {
List<Trait> savedTraits = new ArrayList<>();
for (TraitBaseClass request : body) {

View File

@@ -0,0 +1,28 @@
package org.brapi.test.BrAPITestServer.system.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String secretKey = "change-me-in-production";
private int accessTokenExpireMinutes = 1440;
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public int getAccessTokenExpireMinutes() {
return accessTokenExpireMinutes;
}
public void setAccessTokenExpireMinutes(int accessTokenExpireMinutes) {
this.accessTokenExpireMinutes = accessTokenExpireMinutes;
}
}

View File

@@ -0,0 +1,9 @@
package org.brapi.test.BrAPITestServer.system.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableJpaRepositories(basePackages = "org.brapi.test.BrAPITestServer.system.repository")
public class SystemJpaConfig {
}

View File

@@ -0,0 +1,36 @@
package org.brapi.test.BrAPITestServer.system.controller;
import jakarta.validation.Valid;
import org.brapi.test.BrAPITestServer.system.dto.LoginRequest;
import org.brapi.test.BrAPITestServer.system.dto.RegisterRequest;
import org.brapi.test.BrAPITestServer.system.dto.TokenResponse;
import org.brapi.test.BrAPITestServer.system.service.AuthService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/auth")
@CrossOrigin
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/register")
public ResponseEntity<TokenResponse> register(@Valid @RequestBody RegisterRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));
}
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@Valid @RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.login(request));
}
}

View File

@@ -0,0 +1,39 @@
package org.brapi.test.BrAPITestServer.system.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AuthErrorResponse {
private String message;
private Object detail;
public AuthErrorResponse() {
}
public AuthErrorResponse(String message) {
this.message = message;
this.detail = message;
}
public AuthErrorResponse(String message, Object detail) {
this.message = message;
this.detail = detail;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getDetail() {
return detail;
}
public void setDetail(Object detail) {
this.detail = detail;
}
}

View File

@@ -0,0 +1,31 @@
package org.brapi.test.BrAPITestServer.system.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class LoginRequest {
@NotBlank
@Size(min = 1, max = 100)
private String account;
@NotBlank
@Size(min = 1, max = 128)
private String password;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,68 @@
package org.brapi.test.BrAPITestServer.system.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class RegisterRequest {
@NotBlank
@Size(min = 3, max = 100)
private String account;
@NotBlank
@Size(min = 6, max = 128)
private String password;
@NotBlank
@Size(min = 1, max = 100)
private String name;
@NotBlank
@Email
@Size(min = 3, max = 255)
private String email;
@Size(max = 50)
private String phone;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}

View File

@@ -0,0 +1,49 @@
package org.brapi.test.BrAPITestServer.system.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType = "bearer";
@JsonProperty("expires_at")
private String expiresAt;
private UserResponse user;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(String expiresAt) {
this.expiresAt = expiresAt;
}
public UserResponse getUser() {
return user;
}
public void setUser(UserResponse user) {
this.user = user;
}
}

View File

@@ -0,0 +1,54 @@
package org.brapi.test.BrAPITestServer.system.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class UserResponse {
private UUID id;
private String account;
private String name;
private String email;
private String phone;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}

View File

@@ -0,0 +1,119 @@
package org.brapi.test.BrAPITestServer.system.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "sys_users")
public class SysUserEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, length = 100, unique = true)
private String account;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, length = 255, unique = true)
private String email;
@Column(length = 50)
private String phone;
@Column(name = "created_at", nullable = false)
private Instant createdAt;
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
@PrePersist
void onCreate() {
Instant now = Instant.now();
createdAt = now;
updatedAt = now;
}
@PreUpdate
void onUpdate() {
updatedAt = Instant.now();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,119 @@
package org.brapi.test.BrAPITestServer.system.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "user_sessions")
public class UserSessionEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "user_id", nullable = false)
private UUID userId;
@Column(nullable = false, length = 64, unique = true)
private String jti;
@Column(name = "token_hash", nullable = false)
private String tokenHash;
@Column(name = "expires_at", nullable = false)
private Instant expiresAt;
@Column(name = "revoked_at")
private Instant revokedAt;
@Column(name = "created_at", nullable = false)
private Instant createdAt;
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
@PrePersist
void onCreate() {
Instant now = Instant.now();
createdAt = now;
updatedAt = now;
}
@PreUpdate
void onUpdate() {
updatedAt = Instant.now();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public String getJti() {
return jti;
}
public void setJti(String jti) {
this.jti = jti;
}
public String getTokenHash() {
return tokenHash;
}
public void setTokenHash(String tokenHash) {
this.tokenHash = tokenHash;
}
public Instant getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(Instant expiresAt) {
this.expiresAt = expiresAt;
}
public Instant getRevokedAt() {
return revokedAt;
}
public void setRevokedAt(Instant revokedAt) {
this.revokedAt = revokedAt;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,17 @@
package org.brapi.test.BrAPITestServer.system.exception;
import org.springframework.http.HttpStatus;
public class AuthApiException extends RuntimeException {
private final HttpStatus status;
public AuthApiException(HttpStatus status, String message) {
super(message);
this.status = status;
}
public HttpStatus getStatus() {
return status;
}
}

View File

@@ -0,0 +1,46 @@
package org.brapi.test.BrAPITestServer.system.exception;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.brapi.test.BrAPITestServer.system.dto.AuthErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.LinkedHashMap;
import java.util.Map;
@RestControllerAdvice(basePackages = "org.brapi.test.BrAPITestServer.system")
public class AuthExceptionHandler {
@ExceptionHandler(AuthApiException.class)
public ResponseEntity<AuthErrorResponse> handleAuthApiException(AuthApiException ex) {
String message = ex.getMessage();
return ResponseEntity.status(ex.getStatus()).body(new AuthErrorResponse(message, message));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<AuthErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
Map<String, String> fieldErrors = new LinkedHashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
fieldErrors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.body(new AuthErrorResponse("Validation failed", fieldErrors));
}
@ExceptionHandler(TokenExpiredException.class)
public ResponseEntity<AuthErrorResponse> handleTokenExpired(TokenExpiredException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new AuthErrorResponse("Token expired", "Token expired"));
}
@ExceptionHandler(JWTVerificationException.class)
public ResponseEntity<AuthErrorResponse> handleJwtVerification(JWTVerificationException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new AuthErrorResponse("Invalid token signature", "Invalid token signature"));
}
}

View File

@@ -0,0 +1,16 @@
package org.brapi.test.BrAPITestServer.system.repository;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface SysUserRepository extends JpaRepository<SysUserEntity, UUID> {
Optional<SysUserEntity> findByAccount(String account);
boolean existsByAccount(String account);
boolean existsByEmail(String email);
}

View File

@@ -0,0 +1,29 @@
package org.brapi.test.BrAPITestServer.system.repository;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.entity.UserSessionEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
public interface UserSessionRepository extends JpaRepository<UserSessionEntity, UUID> {
@Query("""
SELECT u FROM SysUserEntity u
JOIN UserSessionEntity s ON s.userId = u.id
WHERE u.id = :userId
AND s.jti = :jti
AND s.tokenHash = :tokenHash
AND s.revokedAt IS NULL
AND s.expiresAt > :now
""")
Optional<SysUserEntity> findActiveUserBySession(
@Param("userId") UUID userId,
@Param("jti") String jti,
@Param("tokenHash") String tokenHash,
@Param("now") Instant now);
}

View File

@@ -0,0 +1,136 @@
package org.brapi.test.BrAPITestServer.system.security;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.brapi.test.BrAPITestServer.auth.AuthDetails;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.service.TokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
@Component
public class SystemJwtAuthFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(SystemJwtAuthFilter.class);
private static final String ANONYMOUS_KEY = "anonymousKey";
private final TokenService tokenService;
private final boolean authEnabled;
public SystemJwtAuthFilter(
TokenService tokenService,
@Value("${security.enabled:true}") boolean authEnabled) {
this.tokenService = tokenService;
this.authEnabled = authEnabled;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String path = request.getRequestURI();
return path.startsWith("/auth/");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!authEnabled) {
setAuthenticatedUser("anonymousUser", Long.MAX_VALUE, true);
filterChain.doFilter(request, response);
return;
}
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
setAnonymousUser();
filterChain.doFilter(request, response);
return;
}
String token = header.substring("Bearer ".length()).trim();
try {
SysUserEntity user = tokenService.resolveActiveUser(token)
.orElseThrow(() -> new JWTVerificationException("Session invalid or expired"));
var decoded = tokenService.verifyToken(token);
Date expiresAt = decoded.getExpiresAt();
long expirationMillis = expiresAt != null ? expiresAt.getTime() : Long.MAX_VALUE;
setAuthenticatedUser(user.getId().toString(), expirationMillis, false);
filterChain.doFilter(request, response);
} catch (TokenExpiredException ex) {
writeAuthError(response, HttpStatus.FORBIDDEN, "Token expired");
} catch (JWTVerificationException ex) {
writeAuthError(response, HttpStatus.UNAUTHORIZED,
ex.getMessage() != null ? ex.getMessage() : "Invalid token signature");
} catch (Exception ex) {
log.error("Authentication failed", ex);
writeAuthError(response, HttpStatus.UNAUTHORIZED, "Session invalid or expired");
}
}
private void setAnonymousUser() {
AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken(
ANONYMOUS_KEY,
"anonymousUser",
List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
AuthDetails details = new AuthDetails();
details.setUserId("anonymousUser");
details.setExpirationTimestamp(Long.MAX_VALUE);
details.addRole("ROLE_ANONYMOUS");
authentication.setDetails(details);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private void setAuthenticatedUser(String userId, long expirationMillis, boolean grantAdmin) {
List<SimpleGrantedAuthority> authorities = grantAdmin
? List.of(
new SimpleGrantedAuthority("ROLE_USER"),
new SimpleGrantedAuthority("ROLE_ADMIN"),
new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
: List.of(new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userId, null, authorities);
AuthDetails details = new AuthDetails();
details.setUserId(userId);
details.setExpirationTimestamp(expirationMillis);
details.addRole("ROLE_USER");
if (grantAdmin) {
details.addRole("ROLE_ADMIN");
details.addRole("ROLE_ANONYMOUS");
}
authentication.setDetails(details);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private void writeAuthError(HttpServletResponse response, HttpStatus status, String message) throws IOException {
response.setStatus(status.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
String body = "{\"message\":\"" + escapeJson(message) + "\",\"detail\":\"" + escapeJson(message) + "\"}";
response.getWriter().write(body);
}
private String escapeJson(String value) {
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
}

View File

@@ -0,0 +1,78 @@
package org.brapi.test.BrAPITestServer.system.service;
import org.brapi.test.BrAPITestServer.system.dto.LoginRequest;
import org.brapi.test.BrAPITestServer.system.dto.RegisterRequest;
import org.brapi.test.BrAPITestServer.system.dto.TokenResponse;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.exception.AuthApiException;
import org.brapi.test.BrAPITestServer.system.repository.SysUserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AuthService {
private final SysUserRepository sysUserRepository;
private final PasswordService passwordService;
private final TokenService tokenService;
public AuthService(
SysUserRepository sysUserRepository,
PasswordService passwordService,
TokenService tokenService) {
this.sysUserRepository = sysUserRepository;
this.passwordService = passwordService;
this.tokenService = tokenService;
}
@Transactional
public TokenResponse register(RegisterRequest request) {
String account = normalizeAccount(request.getAccount());
String email = normalizeEmail(request.getEmail());
String phone = normalizeOptional(request.getPhone());
if (sysUserRepository.existsByAccount(account) || sysUserRepository.existsByEmail(email)) {
throw new AuthApiException(HttpStatus.CONFLICT, "Account or email already exists");
}
SysUserEntity user = new SysUserEntity();
user.setAccount(account);
user.setPasswordHash(passwordService.hashPassword(request.getPassword()));
user.setName(request.getName().trim());
user.setEmail(email);
user.setPhone(phone);
sysUserRepository.save(user);
return tokenService.issueToken(user);
}
@Transactional
public TokenResponse login(LoginRequest request) {
String account = normalizeAccount(request.getAccount());
SysUserEntity user = sysUserRepository.findByAccount(account)
.orElseThrow(() -> new AuthApiException(HttpStatus.UNAUTHORIZED, "Invalid account or password"));
if (!passwordService.verifyPassword(request.getPassword(), user.getPasswordHash())) {
throw new AuthApiException(HttpStatus.UNAUTHORIZED, "Invalid account or password");
}
return tokenService.issueToken(user);
}
private String normalizeAccount(String account) {
return account == null ? "" : account.trim();
}
private String normalizeEmail(String email) {
return email == null ? "" : email.trim().toLowerCase();
}
private String normalizeOptional(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
}

View File

@@ -0,0 +1,71 @@
package org.brapi.test.BrAPITestServer.system.service;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.HexFormat;
@Service
public class PasswordService {
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int ITERATIONS = 210_000;
private static final int KEY_LENGTH = 256;
private static final int SALT_BYTES = 16;
public String hashPassword(String password) {
byte[] salt = new byte[SALT_BYTES];
new SecureRandom().nextBytes(salt);
String digest = pbkdf2(password, salt);
return "pbkdf2_sha256$" + ITERATIONS + "$" + HexFormat.of().formatHex(salt) + "$" + digest;
}
public boolean verifyPassword(String password, String passwordHash) {
if (passwordHash == null || passwordHash.isBlank()) {
return false;
}
String[] parts = passwordHash.split("\\$");
if (parts.length != 4 || !"pbkdf2_sha256".equals(parts[0])) {
return false;
}
int iterations;
try {
iterations = Integer.parseInt(parts[1]);
} catch (NumberFormatException ex) {
return false;
}
byte[] salt = HexFormat.of().parseHex(parts[2]);
String expected = parts[3];
String actual = pbkdf2(password, salt, iterations);
return constantTimeEquals(expected, actual);
}
private String pbkdf2(String password, byte[] salt) {
return pbkdf2(password, salt, ITERATIONS);
}
private String pbkdf2(String password, byte[] salt, int iterations) {
try {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
return HexFormat.of().formatHex(factory.generateSecret(spec).getEncoded());
} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
throw new IllegalStateException("Failed to hash password", ex);
}
}
private boolean constantTimeEquals(String left, String right) {
if (left == null || right == null || left.length() != right.length()) {
return false;
}
int result = 0;
for (int i = 0; i < left.length(); i++) {
result |= left.charAt(i) ^ right.charAt(i);
}
return result == 0;
}
}

View File

@@ -0,0 +1,117 @@
package org.brapi.test.BrAPITestServer.system.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.brapi.test.BrAPITestServer.system.config.JwtProperties;
import org.brapi.test.BrAPITestServer.system.dto.TokenResponse;
import org.brapi.test.BrAPITestServer.system.dto.UserResponse;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.entity.UserSessionEntity;
import org.brapi.test.BrAPITestServer.system.repository.UserSessionRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HexFormat;
import java.util.Optional;
import java.util.UUID;
@Service
public class TokenService {
private static final DateTimeFormatter ISO_FORMATTER =
DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneOffset.UTC);
private final JwtProperties jwtProperties;
private final UserSessionRepository userSessionRepository;
public TokenService(JwtProperties jwtProperties, UserSessionRepository userSessionRepository) {
this.jwtProperties = jwtProperties;
this.userSessionRepository = userSessionRepository;
}
@Transactional
public TokenResponse issueToken(SysUserEntity user) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(jwtProperties.getAccessTokenExpireMinutes(), ChronoUnit.MINUTES);
String jti = generateJti();
String accessToken = createAccessToken(user, jti, issuedAt, expiresAt);
UserSessionEntity session = new UserSessionEntity();
session.setUserId(user.getId());
session.setJti(jti);
session.setTokenHash(hashToken(accessToken));
session.setExpiresAt(expiresAt);
userSessionRepository.save(session);
TokenResponse response = new TokenResponse();
response.setAccessToken(accessToken);
response.setExpiresAt(ISO_FORMATTER.format(expiresAt));
response.setUser(toUserResponse(user));
return response;
}
public Optional<SysUserEntity> resolveActiveUser(String accessToken) {
DecodedJWT jwt = verifyToken(accessToken);
UUID userId = UUID.fromString(jwt.getSubject());
String jti = jwt.getId();
return userSessionRepository.findActiveUserBySession(
userId, jti, hashToken(accessToken), Instant.now());
}
public DecodedJWT verifyToken(String accessToken) {
try {
Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey());
return JWT.require(algorithm).build().verify(accessToken);
} catch (JWTVerificationException | IllegalArgumentException ex) {
throw ex;
}
}
private String createAccessToken(SysUserEntity user, String jti, Instant issuedAt, Instant expiresAt) {
Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey());
return JWT.create()
.withSubject(user.getId().toString())
.withClaim("account", user.getAccount())
.withJWTId(jti)
.withIssuedAt(Date.from(issuedAt))
.withExpiresAt(Date.from(expiresAt))
.sign(algorithm);
}
private String generateJti() {
byte[] bytes = new byte[16];
new SecureRandom().nextBytes(bytes);
return HexFormat.of().formatHex(bytes);
}
public String hashToken(String accessToken) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashed = digest.digest(accessToken.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hashed);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("SHA-256 not available", ex);
}
}
public UserResponse toUserResponse(SysUserEntity user) {
UserResponse response = new UserResponse();
response.setId(user.getId());
response.setAccount(user.getAccount());
response.setName(user.getName());
response.setEmail(user.getEmail());
response.setPhone(user.getPhone());
return response;
}
}

View File

@@ -1,6 +1,4 @@
server.port = 8080
server.servlet.context-path=/brapi/v2
spring.datasource.url=jdbc:postgresql://localhost:5432/brapi-java
spring.datasource.username=postgres
@@ -18,5 +16,7 @@ spring.flyway.baselineOnMigrate=true
spring.mvc.dispatch-options-request=true
security.oidc_discovery_url=https://example.com/auth/.well-known/openid-configuration
security.issuer_url=http://example.com/issuerurl
security.enabled=true
jwt.secret-key=change-me-in-production
jwt.access-token-expire-minutes=1440

View File

@@ -0,0 +1,30 @@
-- Auth tables for login/register (sys_users + user_sessions)
-- See docs/dev/backend/auth.md
CREATE TABLE IF NOT EXISTS sys_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account VARCHAR(100) NOT NULL,
password_hash TEXT NOT NULL,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS ux_users_account ON sys_users (account);

File diff suppressed because one or more lines are too long