From e60f63798b375b9b1b2033cb2db0edb9bdef8a2e Mon Sep 17 00:00:00 2001 From: Justin-MacIntosh Date: Sat, 17 Jan 2026 16:02:01 -0500 Subject: [PATCH 1/2] chore: Updated security and organization of CustomBenefit API Endpoints. --- .../org/acme/constants/CollectionNames.java | 3 - .../org/acme/controller/BenefitResource.java | 89 ---- .../controller/CustomBenefitResource.java | 483 ++++++++++++++++++ .../org/acme/controller/DecisionResource.java | 14 +- .../controller/EligibilityCheckResource.java | 10 +- .../acme/controller/LibraryCheckResource.java | 5 +- .../org/acme/controller/ScreenerResource.java | 227 +------- .../java/org/acme/mapper/DmnModelMapper.java | 2 +- .../java/org/acme/model/domain/Benefit.java | 29 +- .../org/acme/model/domain/BenefitDetail.java | 20 +- .../org/acme/model/domain/CheckConfig.java | 23 + .../acme/model/dto/CreateCheckRequest.java | 11 - .../dto/CustomBenefit/AddCheckRequest.java | 5 + .../CreateCustomBenefitRequest.java | 6 + .../UpdateCheckParametersRequest.java | 7 + .../UpdateCustomBenefitRequest.java | 6 + .../acme/model/dto/{ => Dmn}/Dependency.java | 2 +- .../model/dto/{ => Dmn}/DmnImportRequest.java | 2 +- .../model/dto/{ => Dmn}/DmnModelSummary.java | 2 +- .../EligibilityCheck/CreateCheckRequest.java | 11 + .../EvaluateCheckRequest.java | 2 +- .../dto/{ => Screener}/FormPathsResponse.java | 2 +- .../PublishScreenerRequest.java | 2 +- .../dto/{ => Screener}/SaveSchemaRequest.java | 2 +- .../EligibilityCheckRepository.java | 8 - .../impl/BenefitRepositoryImpl.java | 40 -- .../impl/EligibilityCheckRepositoryImpl.java | 50 -- .../org/acme/service/LibraryApiService.java | 6 +- .../acme/service/InputSchemaServiceTest.java | 1 - builder-frontend/src/api/benefit.ts | 67 ++- builder-frontend/src/api/screener.ts | 15 +- .../components/homeScreen/ProjectsList.tsx | 2 +- .../benefitList/modals/AddNewBenefitModal.tsx | 8 +- .../modals/SelectExistingBenefitModal.tsx | 76 --- .../benefitList/screenerBenefitsResource.ts | 6 +- .../configureBenefit/ConfigureBenefit.tsx | 10 +- .../EligibilityCheckListView.tsx | 18 +- .../configureBenefit/benefitResource.ts | 69 +-- builder-frontend/src/types.ts | 20 +- 39 files changed, 715 insertions(+), 646 deletions(-) delete mode 100644 builder-api/src/main/java/org/acme/controller/BenefitResource.java create mode 100644 builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java delete mode 100644 builder-api/src/main/java/org/acme/model/dto/CreateCheckRequest.java create mode 100644 builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java create mode 100644 builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java create mode 100644 builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java create mode 100644 builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java rename builder-api/src/main/java/org/acme/model/dto/{ => Dmn}/Dependency.java (92%) rename builder-api/src/main/java/org/acme/model/dto/{ => Dmn}/DmnImportRequest.java (82%) rename builder-api/src/main/java/org/acme/model/dto/{ => Dmn}/DmnModelSummary.java (86%) create mode 100644 builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/CreateCheckRequest.java rename builder-api/src/main/java/org/acme/model/dto/{ => EligibilityCheck}/EvaluateCheckRequest.java (80%) rename builder-api/src/main/java/org/acme/model/dto/{ => Screener}/FormPathsResponse.java (93%) rename builder-api/src/main/java/org/acme/model/dto/{ => Screener}/PublishScreenerRequest.java (65%) rename builder-api/src/main/java/org/acme/model/dto/{ => Screener}/SaveSchemaRequest.java (87%) delete mode 100644 builder-api/src/main/java/org/acme/persistence/impl/BenefitRepositoryImpl.java delete mode 100644 builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx diff --git a/builder-api/src/main/java/org/acme/constants/CollectionNames.java b/builder-api/src/main/java/org/acme/constants/CollectionNames.java index dc72c0c1..2fb3cff7 100644 --- a/builder-api/src/main/java/org/acme/constants/CollectionNames.java +++ b/builder-api/src/main/java/org/acme/constants/CollectionNames.java @@ -5,7 +5,4 @@ public class CollectionNames { public static final String PUBLISHED_SCREENER_COLLECTION = "publishedScreener"; public static final String WORKING_CUSTOM_CHECK_COLLECTION = "workingCustomCheck"; public static final String PUBLISHED_CUSTOM_CHECK_COLLECTION = "publishedCustomCheck"; - public static final String BENEFIT_COLLECTION = "benefit"; - public static final String PUBLIC_BENEFIT_COLLECTION = "publicBenefit"; - public static final String PUBLIC_CHECK_COLLECTION = "publicCheck"; } diff --git a/builder-api/src/main/java/org/acme/controller/BenefitResource.java b/builder-api/src/main/java/org/acme/controller/BenefitResource.java deleted file mode 100644 index 3c1e6619..00000000 --- a/builder-api/src/main/java/org/acme/controller/BenefitResource.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.acme.controller; - -import io.quarkus.logging.Log; -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.acme.auth.AuthUtils; -import org.acme.model.domain.Benefit; -import org.acme.model.domain.EligibilityCheck; -import org.acme.persistence.BenefitRepository; -import org.acme.persistence.EligibilityCheckRepository; - -import java.util.List; -import java.util.Optional; - -@Path("/api") -public class BenefitResource { - - @Inject - BenefitRepository benefitRepository; - - @Inject - EligibilityCheckRepository eligibilityCheckRepository; - - @GET - @Path("/benefit") - public Response getAllBenefits(@Context SecurityIdentity identity) { - String userId = AuthUtils.getUserId(identity); - if (userId == null){ - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - Log.info("Fetching all eligibility checks. User: " + userId); - List benefits = benefitRepository.getAllBenefits(); - - return Response.ok(benefits, MediaType.APPLICATION_JSON).build(); - } - - @GET - @Path("/benefit/{benefitId}") - public Response getBenefit(@Context SecurityIdentity identity, - @PathParam("benefitId") String benefitId) { - String userId = AuthUtils.getUserId(identity); - if (userId == null){ - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - Log.info("Fetching benefit: " + benefitId + " for user: " + userId); - Optional benefitOpt = benefitRepository.getBenefit(benefitId); - - if (benefitOpt.isEmpty()){ - return Response.status(Response.Status.NOT_FOUND).build(); - } - - Benefit benefit = benefitOpt.get(); - - if (!benefit.getPublic() && !benefit.getOwnerId().equals(userId)){ - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - return Response.ok(benefit, MediaType.APPLICATION_JSON).build(); - } - - - // Get all of the full Eligibility Check Objects that have been added to a Public Benefit - @GET - @Path("/benefit/{benefitId}/check") - public Response getBenefitChecks(@Context SecurityIdentity identity, - @PathParam("benefitId") String benefitId) { - String userId = AuthUtils.getUserId(identity); - if (userId == null){ - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - Log.info("Fetching all eligibility checks for Benefit: " + benefitId + " User: " + userId); - Optional benefitOpt = benefitRepository.getBenefit(benefitId); - - if (benefitOpt.isEmpty()){ - return Response.status(Response.Status.NOT_FOUND).build(); - } - Benefit benefit = benefitOpt.get(); - - if (!benefit.getPublic() && !benefit.getOwnerId().equals(userId)){ - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - List checks = eligibilityCheckRepository.getChecksInBenefit(benefit); - - return Response.ok(checks, MediaType.APPLICATION_JSON).build(); - } -} diff --git a/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java b/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java new file mode 100644 index 00000000..0dee07fb --- /dev/null +++ b/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java @@ -0,0 +1,483 @@ +package org.acme.controller; + +import io.quarkus.logging.Log; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.acme.auth.AuthUtils; +import org.acme.model.domain.*; +import org.acme.model.dto.CustomBenefit.AddCheckRequest; +import org.acme.model.dto.CustomBenefit.CreateCustomBenefitRequest; +import org.acme.model.dto.CustomBenefit.UpdateCheckParametersRequest; +import org.acme.model.dto.CustomBenefit.UpdateCustomBenefitRequest; +import org.acme.persistence.EligibilityCheckRepository; +import org.acme.persistence.ScreenerRepository; +import org.acme.service.LibraryApiService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +@Path("/api") +public class CustomBenefitResource { + + @Inject + ScreenerRepository screenerRepository; + + @Inject + EligibilityCheckRepository eligibilityCheckRepository; + + @Inject + LibraryApiService libraryApiMetadataService; // Inject the singleton bean + + // ========== Collection Endpoints ========== + + @GET + @Path("/screener/{screenerId}/benefit") + public Response getCustomBenefits( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId + ) { + String userId = AuthUtils.getUserId(identity); + + Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); + if (screenerOpt.isEmpty()) { + throw new NotFoundException(); + } + Screener screener = screenerOpt.get(); + + if (!isUserAuthorizedForScreener(userId, screener)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + List benefits = screenerRepository.getBenefitsInScreener(screener); + return Response.ok().entity(benefits).build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not fetch benefits")) + .build(); + } + } + + @POST + @Path("/screener/{screenerId}/benefit") + @Consumes(MediaType.APPLICATION_JSON) + public Response createCustomBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + CreateCustomBenefitRequest request + ) { + String userId = AuthUtils.getUserId(identity); + + // Validate request + if (request.name == null || request.name.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Name is required")) + .build(); + } + + // Create new Benefit with server-generated ID + String newBenefitId = UUID.randomUUID().toString(); + Benefit newBenefit = new Benefit( + newBenefitId, + request.name, + request.description, + userId, + Collections.emptyList() + ); + + // Create corresponding BenefitDetail + BenefitDetail benefitDetail = new BenefitDetail(newBenefitId, request.name, request.description); + + try { + // Check to make sure screener exists + Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); + if (screenerOpt.isEmpty()) { + Log.error("Screener not found. Screener ID:" + screenerId); + throw new NotFoundException(); + } + + Screener screener = screenerOpt.get(); + + // Authorize action + if (!isUserAuthorizedForScreener(userId, screener)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + screenerRepository.saveNewCustomBenefit(screenerId, newBenefit); + screenerRepository.addBenefitDetailToWorkingScreener(screenerId, benefitDetail); + return Response.ok(newBenefit, MediaType.APPLICATION_JSON).build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not save benefit")) + .build(); + } + } + + // ========== Single Resource Endpoints ========== + + @GET + @Path("/screener/{screenerId}/benefit/{benefitId}") + public Response getCustomBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId + ) { + String userId = AuthUtils.getUserId(identity); + if (!isUserAuthorizedForScreener(userId, screenerId)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (benefitOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok().entity(benefitOpt.get()).build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not fetch benefit")) + .build(); + } + } + + @PATCH + @Consumes(MediaType.APPLICATION_JSON) + @Path("/screener/{screenerId}/benefit/{benefitId}") + public Response updateCustomBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId, + UpdateCustomBenefitRequest request + ) { + String userId = AuthUtils.getUserId(identity); + + if (!isUserAuthorizedForScreener(userId, screenerId)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + // Get the existing benefit + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (benefitOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + Benefit existingBenefit = benefitOpt.get(); + + // Only update name and description, preserve everything else + if (request.name != null) { + existingBenefit.setName(request.name); + } + if (request.description != null) { + existingBenefit.setDescription(request.description); + } + + screenerRepository.updateCustomBenefit(screenerId, existingBenefit); + + // Also update the BenefitDetail in the screener's benefits list + Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); + if (screenerOpt.isPresent()) { + Screener screener = screenerOpt.get(); + List benefits = screener.getBenefits(); + if (benefits != null) { + List updatedBenefits = new ArrayList<>(); + for (BenefitDetail detail : benefits) { + if (detail.getId().equals(benefitId)) { + // Update name and description + if (request.name != null) { + detail.setName(request.name); + } + if (request.description != null) { + detail.setDescription(request.description); + } + } + updatedBenefits.add(detail); + } + screener.setBenefits(updatedBenefits); + screenerRepository.updateWorkingScreener(screener); + } + } + + return Response.ok(existingBenefit, MediaType.APPLICATION_JSON).build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not update custom benefit")) + .build(); + } + } + + @DELETE + @Path("/screener/{screenerId}/benefit/{benefitId}") + public Response deleteCustomBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId + ) { + try { + // Check if Screener and Benefit exist + Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (screenerOpt.isEmpty()) { + throw new NotFoundException(); + } + if (benefitOpt.isEmpty()) { + throw new NotFoundException(); + } + + // Confirm user is authorized to make the change + String userId = AuthUtils.getUserId(identity); + Screener screener = screenerOpt.get(); + if (!isUserAuthorizedForScreener(userId, screener)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + // Delete the benefit and remove the benefitDetail from the screener + screenerRepository.deleteCustomBenefit(screenerId, benefitId); + List updatedBenefits = screener.getBenefits() + .stream() + .filter(benefitDetail -> !benefitDetail.getId().equals(benefitId)) + .toList(); + screener.setBenefits(updatedBenefits); + screenerRepository.updateWorkingScreener(screener); + + return Response.ok().build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not delete custom benefit")) + .build(); + } + } + + // ========== Sub-Resource Endpoints: Checks ========== + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/screener/{screenerId}/benefit/{benefitId}/check") + public Response addCheckToBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId, + AddCheckRequest request + ) { + String userId = AuthUtils.getUserId(identity); + + // Validate request + if (request.checkId == null || request.checkId.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "checkId is required")) + .build(); + } + + if (!isUserAuthorizedForScreener(userId, screenerId)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + // Get the benefit + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (benefitOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Benefit not found")) + .build(); + } + + // Find the EligibilityCheck - first try user's custom checks, then library checks + Optional checkOpt = eligibilityCheckRepository.getPublishedCustomCheck(userId, request.checkId); + if (checkOpt.isEmpty()) { + checkOpt = libraryApiMetadataService.getById(request.checkId); + } + + if (checkOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "EligibilityCheck not found")) + .build(); + } + + EligibilityCheck check = checkOpt.get(); + Benefit benefit = benefitOpt.get(); + + // Create CheckConfig snapshot from the EligibilityCheck + CheckConfig checkConfig = new CheckConfig( + check.getId(), + check.getName(), + check.getVersion(), + check.getModule(), + check.getEvaluationUrl(), + check.getInputDefinition(), + check.getParameterDefinitions(), + new HashMap<>() + ); + + // Add the check to the benefit + List checks = benefit.getChecks(); + if (checks == null) { + checks = new ArrayList<>(); + } else { + checks = new ArrayList<>(checks); + } + checks.add(checkConfig); + benefit.setChecks(checks); + + // Save the updated benefit + screenerRepository.updateCustomBenefit(screenerId, benefit); + + return Response.ok().build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not add check to benefit")) + .build(); + } + } + + @DELETE + @Path("/screener/{screenerId}/benefit/{benefitId}/check/{checkId}") + public Response removeCheckFromBenefit( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId, + @PathParam("checkId") String checkId + ) { + String userId = AuthUtils.getUserId(identity); + + if (!isUserAuthorizedForScreener(userId, screenerId)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + // Get the benefit + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (benefitOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Benefit not found")) + .build(); + } + + Benefit benefit = benefitOpt.get(); + List checks = benefit.getChecks(); + + if (checks == null || checks.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Check not found in benefit")) + .build(); + } + + // Remove the check with the matching checkId + List updatedChecks = checks.stream() + .filter(check -> !check.getCheckId().equals(checkId)) + .toList(); + + if (updatedChecks.size() == checks.size()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Check not found in benefit")) + .build(); + } + + benefit.setChecks(new ArrayList<>(updatedChecks)); + + // Save the updated benefit + screenerRepository.updateCustomBenefit(screenerId, benefit); + + return Response.ok().build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not remove check from benefit")) + .build(); + } + } + + @PATCH + @Consumes(MediaType.APPLICATION_JSON) + @Path("/screener/{screenerId}/benefit/{benefitId}/check/{checkId}/parameters") + public Response updateCheckParameters( + @Context SecurityIdentity identity, + @PathParam("screenerId") String screenerId, + @PathParam("benefitId") String benefitId, + @PathParam("checkId") String checkId, + UpdateCheckParametersRequest request + ) { + String userId = AuthUtils.getUserId(identity); + + if (!isUserAuthorizedForScreener(userId, screenerId)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + try { + // Get the benefit + Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); + if (benefitOpt.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Benefit not found")) + .build(); + } + + Benefit benefit = benefitOpt.get(); + List checks = benefit.getChecks(); + + if (checks == null || checks.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Check not found in benefit")) + .build(); + } + + // Find and update the check with the matching checkId + Boolean checkUpdated = false; + List checkListAfterUpdate = new ArrayList<>(); + for (CheckConfig check : checks) { + if (check.getCheckId().equals(checkId)) { + check.setParameters(request.parameters != null ? request.parameters : new HashMap<>()); + checkUpdated = true; + } + checkListAfterUpdate.add(check); + } + + if (!checkUpdated) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Check not found in benefit")) + .build(); + } + + benefit.setChecks(checkListAfterUpdate); + + // Save the updated benefit + screenerRepository.updateCustomBenefit(screenerId, benefit); + + return Response.ok().build(); + } catch (Exception e) { + Log.error(e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Could not update check parameters")) + .build(); + } + } + + // ========== Private Helper Methods ========== + + private boolean isUserAuthorizedForScreener(String userId, String screenerId) { + Optional screenerOptional = screenerRepository.getWorkingScreenerMetaDataOnly(screenerId); + if (screenerOptional.isEmpty()) { + return false; + } + return isUserAuthorizedForScreener(userId, screenerOptional.get()); + } + + private boolean isUserAuthorizedForScreener(String userId, Screener screener) { + String ownerId = screener.getOwnerId(); + return userId != null && userId.equals(ownerId); + } +} diff --git a/builder-api/src/main/java/org/acme/controller/DecisionResource.java b/builder-api/src/main/java/org/acme/controller/DecisionResource.java index 4a2e48d6..4e57f74c 100644 --- a/builder-api/src/main/java/org/acme/controller/DecisionResource.java +++ b/builder-api/src/main/java/org/acme/controller/DecisionResource.java @@ -14,7 +14,7 @@ import org.acme.model.domain.CheckConfig; import org.acme.model.domain.EligibilityCheck; import org.acme.model.domain.Screener; -import org.acme.model.dto.EvaluateCheckRequest; +import org.acme.model.dto.EligibilityCheck.EvaluateCheckRequest; import org.acme.persistence.EligibilityCheckRepository; import org.acme.persistence.PublishedScreenerRepository; import org.acme.persistence.ScreenerRepository; @@ -129,14 +129,9 @@ public Response evaluateScreener( } private Map evaluateBenefit(Benefit benefit, Map formData) throws Exception { - if (benefit.getPublic()) { - // Public benefit, call the Library API to evaluate - Map result = new HashMap<>(); - return result; - } else { - // Custom benefit, evaluate here in the web app api (as opposed to calling the library api for evaluation) - List resultsList = new ArrayList<>(); - Map checkResults = new HashMap<>(); + // Evaluate the benefit using its configured checks + List resultsList = new ArrayList<>(); + Map checkResults = new HashMap<>(); int checkNum = 0; for (CheckConfig checkConfig : benefit.getChecks()) { @@ -186,7 +181,6 @@ private Map evaluateBenefit(Benefit benefit, Map "check_results", checkResults ) ); - } } @POST diff --git a/builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java b/builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java index 827356db..5c81f513 100644 --- a/builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java +++ b/builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java @@ -12,8 +12,8 @@ import org.acme.auth.AuthUtils; import org.acme.constants.CheckStatus; import org.acme.model.domain.EligibilityCheck; -import org.acme.model.dto.CreateCheckRequest; import org.acme.model.dto.EligibilityCheck.CheckDmnRequest; +import org.acme.model.dto.EligibilityCheck.CreateCheckRequest; import org.acme.model.dto.EligibilityCheck.EditCheckRequest; import org.acme.persistence.EligibilityCheckRepository; import org.acme.persistence.StorageService; @@ -71,10 +71,10 @@ public Response createCustomCheck(@Context SecurityIdentity identity, // Build EligibilityCheck from allowed fields only EligibilityCheck newCheck = new EligibilityCheck( - request.name, - request.module, - request.description, - request.parameterDefinitions, + request.name(), + request.module(), + request.description(), + request.parameterDefinitions(), userId ); diff --git a/builder-api/src/main/java/org/acme/controller/LibraryCheckResource.java b/builder-api/src/main/java/org/acme/controller/LibraryCheckResource.java index e782565f..49454e50 100644 --- a/builder-api/src/main/java/org/acme/controller/LibraryCheckResource.java +++ b/builder-api/src/main/java/org/acme/controller/LibraryCheckResource.java @@ -8,6 +8,7 @@ import org.acme.service.LibraryApiService; import java.util.List; +import java.util.Optional; @Path("/api") @Produces(MediaType.APPLICATION_JSON) @@ -29,8 +30,8 @@ public List getLibraryChecks(@QueryParam("module") String modu @Path("/library-checks/{checkId}") public Response getLibraryCheck(@PathParam("checkId") String checkId) { if ( checkId != null) { - EligibilityCheck check = libraryApiMetadataService.getById(checkId); - if (check == null){ + Optional check = libraryApiMetadataService.getById(checkId); + if (check.isEmpty()){ return Response.status(Response.Status.NOT_FOUND).build(); } return Response.ok().entity(check).build(); diff --git a/builder-api/src/main/java/org/acme/controller/ScreenerResource.java b/builder-api/src/main/java/org/acme/controller/ScreenerResource.java index a7b107e5..e32d31ea 100644 --- a/builder-api/src/main/java/org/acme/controller/ScreenerResource.java +++ b/builder-api/src/main/java/org/acme/controller/ScreenerResource.java @@ -13,9 +13,9 @@ import jakarta.ws.rs.core.Response; import org.acme.auth.AuthUtils; import org.acme.model.domain.*; -import org.acme.model.dto.FormPathsResponse; -import org.acme.model.dto.PublishScreenerRequest; -import org.acme.model.dto.SaveSchemaRequest; +import org.acme.model.dto.Screener.FormPathsResponse; +import org.acme.model.dto.Screener.PublishScreenerRequest; +import org.acme.model.dto.Screener.SaveSchemaRequest; import org.acme.persistence.EligibilityCheckRepository; import org.acme.persistence.ScreenerRepository; import org.acme.persistence.PublishedScreenerRepository; @@ -307,225 +307,4 @@ private boolean isUserAuthorizedToAccessScreener(String userId, String screenerI private boolean isUserAuthorizedToAccessScreenerByScreener(String userId, Screener screener) { return userId.equals(screener.getOwnerId()); } - - @GET - @Path("/screener/{screenerId}/benefit") - public Response getScreenerBenefits( - @Context SecurityIdentity identity, @PathParam("screenerId") String screenerId) { - String userId = AuthUtils.getUserId(identity); - - Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); - if (screenerOpt.isEmpty()) { - throw new NotFoundException(); - } - Screener screener = screenerOpt.get(); - - if (!isUserAuthorizedToAccessScreenerByScreener(userId, screener)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - try { - List benefits = screenerRepository.getBenefitsInScreener(screener); - return Response.ok().entity(benefits).build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not fetch benefits")) - .build(); - } - } - - @GET - @Path("/screener/{screenerId}/benefit/{benefitId}") - public Response getScreenerBenefit( - @Context SecurityIdentity identity, - @PathParam("screenerId") String screenerId, - @PathParam("benefitId") String benefitId) { - String userId = AuthUtils.getUserId(identity); - if (!isUserAuthorizedToAccessScreener(userId, screenerId)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - try { - Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); - if (benefitOpt.isEmpty()) { - return Response.status(Response.Status.NOT_FOUND).build(); - } - return Response.ok().entity(benefitOpt.get()).build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not fetch benefit")) - .build(); - } - } - - @GET - @Path("/screener/{screenerId}/benefit/{benefitId}/check") - public Response getScreenerCustomBenefitChecks( - @Context SecurityIdentity identity, - @PathParam("screenerId") String screenerId, - @PathParam("benefitId") String benefitId) { - try { - String userId = AuthUtils.getUserId(identity); - - Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); - if (screenerOpt.isEmpty()) { - throw new NotFoundException(); - } - Screener screener = screenerOpt.get(); - - if (!isUserAuthorizedToAccessScreenerByScreener(userId, screener)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); - if (benefitOpt.isEmpty()) { - throw new NotFoundException(); - } - - List checks = - eligibilityCheckRepository.getChecksInBenefit(benefitOpt.get()); - return Response.ok().entity(checks).build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not fetch checks")) - .build(); - } - } - - @POST - @Path("/screener/{screenerId}/benefit") - public Response addCustomBenefit( - @Context SecurityIdentity identity, - @PathParam("screenerId") String screenerId, - Benefit newBenefit) { - String userId = AuthUtils.getUserId(identity); - - newBenefit.setOwnerId(userId); - newBenefit.setChecks(Collections.emptyList()); - - BenefitDetail benefitDetail = new BenefitDetail(); - benefitDetail.setId(newBenefit.getId()); - benefitDetail.setName(newBenefit.getName()); - benefitDetail.setDescription(newBenefit.getDescription()); - benefitDetail.setPublic(newBenefit.getPublic()); - try { - // Check to make sure not introducing duplicates - Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); - if (screenerOpt.isEmpty()) { - Log.error("Screener not found. Screener ID:" + screenerId); - throw new NotFoundException(); - } - - Screener screener = screenerOpt.get(); - - // Authorise action - if (userId != null && !isUserAuthorizedToAccessScreenerByScreener(userId, screener)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - List benefits = screenerOpt.get().getBenefits(); - if (benefits == null) { - benefits = Collections.emptyList(); - } - Boolean benefitIdExists = - !benefits.stream() - .filter(benefit -> benefit.getId().equals(benefitDetail.getId())) - .toList() - .isEmpty(); - - if (benefitIdExists) { - return Response.status( - Response.Status.CONFLICT.getStatusCode(), - "Benefit with provided ID already exists on screener.") - .build(); - } - - String benefitId = screenerRepository.saveNewCustomBenefit(screenerId, newBenefit); - screenerRepository.addBenefitDetailToWorkingScreener(screenerId, benefitDetail); - newBenefit.setId(benefitId); - return Response.ok(newBenefit, MediaType.APPLICATION_JSON).build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not save benefit")) - .build(); - } - } - - @PUT - @Consumes(MediaType.APPLICATION_JSON) - @Path("/screener/{screenerId}/benefit") - public Response updateCustomBenefit( - @Context SecurityIdentity identity, - @PathParam("screenerId") String screenerId, - Benefit updatedBenefit) { - String userId = AuthUtils.getUserId(identity); - - // TODO: Add validations for user provided data - - if (!isUserAuthorizedToAccessScreener(userId, screenerId)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - try { - Optional benefitOpt = - screenerRepository.getCustomBenefit(screenerId, updatedBenefit.getId()); - if (benefitOpt.isEmpty()) { - return Response.status(Response.Status.NOT_FOUND).build(); - } - - screenerRepository.updateCustomBenefit(screenerId, updatedBenefit); - return Response.ok(updatedBenefit, MediaType.APPLICATION_JSON).build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not update custom benefit")) - .build(); - } - } - - @DELETE - @Path("/screener/{screenerId}/benefit/{benefitId}") - public Response deleteCustomBenefit( - @Context SecurityIdentity identity, - @PathParam("screenerId") String screenerId, - @PathParam("benefitId") String benefitId) { - try { - // Check if Screener and Benefit exist - Optional screenerOpt = screenerRepository.getWorkingScreener(screenerId); - Optional benefitOpt = screenerRepository.getCustomBenefit(screenerId, benefitId); - if (screenerOpt.isEmpty()) { - throw new NotFoundException(); - } - if (benefitOpt.isEmpty()) { - throw new NotFoundException(); - } - - // Confirm user is authorized to make the change - String userId = AuthUtils.getUserId(identity); - Screener screener = screenerOpt.get(); - if (!isUserAuthorizedToAccessScreenerByScreener(userId, screener)) { - return Response.status(Response.Status.UNAUTHORIZED).build(); - } - - // Delete the benefit and remove the benefitDetail from the screener - screenerRepository.deleteCustomBenefit(screenerId, benefitId); - List updatedBenefits = - screener.getBenefits().stream() - .filter(benefitDetail -> !benefitDetail.getId().equals(benefitId)) - .toList(); - screener.setBenefits(updatedBenefits); - screenerRepository.updateWorkingScreener(screener); - - return Response.ok().build(); - } catch (Exception e) { - Log.error(e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity(Map.of("error", "Could not delete custom benefit")) - .build(); - } - } } diff --git a/builder-api/src/main/java/org/acme/mapper/DmnModelMapper.java b/builder-api/src/main/java/org/acme/mapper/DmnModelMapper.java index a8a036eb..53b6f9a8 100644 --- a/builder-api/src/main/java/org/acme/mapper/DmnModelMapper.java +++ b/builder-api/src/main/java/org/acme/mapper/DmnModelMapper.java @@ -2,7 +2,7 @@ import org.acme.constants.FieldNames; import org.acme.model.domain.DmnModel; -import org.acme.model.dto.DmnModelSummary; +import org.acme.model.dto.Dmn.DmnModelSummary; import java.util.HashMap; import java.util.Map; diff --git a/builder-api/src/main/java/org/acme/model/domain/Benefit.java b/builder-api/src/main/java/org/acme/model/domain/Benefit.java index 19fec094..1b1341a4 100644 --- a/builder-api/src/main/java/org/acme/model/domain/Benefit.java +++ b/builder-api/src/main/java/org/acme/model/domain/Benefit.java @@ -1,35 +1,34 @@ package org.acme.model.domain; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) public class Benefit { private String id; - private Boolean isActive; private String description; private String name; private List checks; private String ownerId; - @JsonProperty("isPublic") - private Boolean isPublic; - public String getId() { - return id; + public Benefit() { } - public void setId(String id) { + public Benefit(String id, String name, String description, String ownerId, List checks) { this.id = id; + this.name = name; + this.description = description; + this.ownerId = ownerId; + this.checks = checks; } - public Boolean getActive() { - return isActive; + public String getId() { + return id; } - public void setActive(Boolean active) { - isActive = active; + public void setId(String id) { + this.id = id; } public String getDescription() { @@ -63,12 +62,4 @@ public String getOwnerId() { public void setOwnerId(String ownerId) { this.ownerId = ownerId; } - - public Boolean getPublic() { - return isPublic; - } - - public void setPublic(Boolean aPublic) { - isPublic = aPublic; - } } diff --git a/builder-api/src/main/java/org/acme/model/domain/BenefitDetail.java b/builder-api/src/main/java/org/acme/model/domain/BenefitDetail.java index 365d41e3..70ac5a45 100644 --- a/builder-api/src/main/java/org/acme/model/domain/BenefitDetail.java +++ b/builder-api/src/main/java/org/acme/model/domain/BenefitDetail.java @@ -1,7 +1,6 @@ package org.acme.model.domain; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; /* This object contains the metadata required to display high level information about a Benefit. This is stored directly on @@ -14,8 +13,15 @@ public class BenefitDetail { private String id; private String name; private String description; - @JsonProperty("isPublic") - private Boolean isPublic; + + public BenefitDetail() { + } + + public BenefitDetail(String id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + } public String getId() { return id; @@ -40,12 +46,4 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } - - public Boolean getPublic() { - return isPublic; - } - - public void setPublic(Boolean aPublic) { - isPublic = aPublic; - } } diff --git a/builder-api/src/main/java/org/acme/model/domain/CheckConfig.java b/builder-api/src/main/java/org/acme/model/domain/CheckConfig.java index 0011bb7d..fd834751 100644 --- a/builder-api/src/main/java/org/acme/model/domain/CheckConfig.java +++ b/builder-api/src/main/java/org/acme/model/domain/CheckConfig.java @@ -18,6 +18,29 @@ public class CheckConfig { private JsonNode inputDefinition; private List parameterDefinitions; + public CheckConfig() { + } + + public CheckConfig( + String checkId, + String checkName, + String checkVersion, + String checkModule, + String evaluationUrl, + JsonNode inputDefinition, + List parameterDefinitions, + Map parameters + ) { + this.checkId = checkId; + this.checkName = checkName; + this.checkVersion = checkVersion; + this.checkModule = checkModule; + this.evaluationUrl = evaluationUrl; + this.inputDefinition = inputDefinition; + this.parameterDefinitions = parameterDefinitions; + this.parameters = parameters; + } + public String getCheckId() { return checkId; } diff --git a/builder-api/src/main/java/org/acme/model/dto/CreateCheckRequest.java b/builder-api/src/main/java/org/acme/model/dto/CreateCheckRequest.java deleted file mode 100644 index 656f0e0d..00000000 --- a/builder-api/src/main/java/org/acme/model/dto/CreateCheckRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.acme.model.dto; - -import org.acme.model.domain.ParameterDefinition; -import java.util.List; - -public class CreateCheckRequest { - public String name; - public String module; - public String description; - public List parameterDefinitions; -} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java new file mode 100644 index 00000000..5bcc8cfd --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java @@ -0,0 +1,5 @@ +package org.acme.model.dto.CustomBenefit; + +public class AddCheckRequest { + public String checkId; +} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java new file mode 100644 index 00000000..ac0f71dd --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java @@ -0,0 +1,6 @@ +package org.acme.model.dto.CustomBenefit; + +public class CreateCustomBenefitRequest { + public String name; + public String description; +} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java new file mode 100644 index 00000000..721904ba --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java @@ -0,0 +1,7 @@ +package org.acme.model.dto.CustomBenefit; + +import java.util.Map; + +public class UpdateCheckParametersRequest { + public Map parameters; +} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java new file mode 100644 index 00000000..c136c92d --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java @@ -0,0 +1,6 @@ +package org.acme.model.dto.CustomBenefit; + +public class UpdateCustomBenefitRequest { + public String name; + public String description; +} diff --git a/builder-api/src/main/java/org/acme/model/dto/Dependency.java b/builder-api/src/main/java/org/acme/model/dto/Dmn/Dependency.java similarity index 92% rename from builder-api/src/main/java/org/acme/model/dto/Dependency.java rename to builder-api/src/main/java/org/acme/model/dto/Dmn/Dependency.java index 3e83f8ea..2edc1096 100644 --- a/builder-api/src/main/java/org/acme/model/dto/Dependency.java +++ b/builder-api/src/main/java/org/acme/model/dto/Dmn/Dependency.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Dmn; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/builder-api/src/main/java/org/acme/model/dto/DmnImportRequest.java b/builder-api/src/main/java/org/acme/model/dto/Dmn/DmnImportRequest.java similarity index 82% rename from builder-api/src/main/java/org/acme/model/dto/DmnImportRequest.java rename to builder-api/src/main/java/org/acme/model/dto/Dmn/DmnImportRequest.java index 877bc6cf..bb419ee2 100644 --- a/builder-api/src/main/java/org/acme/model/dto/DmnImportRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/Dmn/DmnImportRequest.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Dmn; public class DmnImportRequest { public String screenerId; diff --git a/builder-api/src/main/java/org/acme/model/dto/DmnModelSummary.java b/builder-api/src/main/java/org/acme/model/dto/Dmn/DmnModelSummary.java similarity index 86% rename from builder-api/src/main/java/org/acme/model/dto/DmnModelSummary.java rename to builder-api/src/main/java/org/acme/model/dto/Dmn/DmnModelSummary.java index 20f27659..2df00cb7 100644 --- a/builder-api/src/main/java/org/acme/model/dto/DmnModelSummary.java +++ b/builder-api/src/main/java/org/acme/model/dto/Dmn/DmnModelSummary.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Dmn; public class DmnModelSummary { public String groupId; diff --git a/builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/CreateCheckRequest.java b/builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/CreateCheckRequest.java new file mode 100644 index 00000000..92ff3391 --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/CreateCheckRequest.java @@ -0,0 +1,11 @@ +package org.acme.model.dto.EligibilityCheck; + +import org.acme.model.domain.ParameterDefinition; +import java.util.List; + +public record CreateCheckRequest( + String name, + String module, + String description, + List parameterDefinitions +) {} diff --git a/builder-api/src/main/java/org/acme/model/dto/EvaluateCheckRequest.java b/builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/EvaluateCheckRequest.java similarity index 80% rename from builder-api/src/main/java/org/acme/model/dto/EvaluateCheckRequest.java rename to builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/EvaluateCheckRequest.java index 6aa390ae..bf3a8898 100644 --- a/builder-api/src/main/java/org/acme/model/dto/EvaluateCheckRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/EligibilityCheck/EvaluateCheckRequest.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.EligibilityCheck; import java.util.Map; diff --git a/builder-api/src/main/java/org/acme/model/dto/FormPathsResponse.java b/builder-api/src/main/java/org/acme/model/dto/Screener/FormPathsResponse.java similarity index 93% rename from builder-api/src/main/java/org/acme/model/dto/FormPathsResponse.java rename to builder-api/src/main/java/org/acme/model/dto/Screener/FormPathsResponse.java index 6dc7162c..9ccbc093 100644 --- a/builder-api/src/main/java/org/acme/model/dto/FormPathsResponse.java +++ b/builder-api/src/main/java/org/acme/model/dto/Screener/FormPathsResponse.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Screener; import java.util.List; diff --git a/builder-api/src/main/java/org/acme/model/dto/PublishScreenerRequest.java b/builder-api/src/main/java/org/acme/model/dto/Screener/PublishScreenerRequest.java similarity index 65% rename from builder-api/src/main/java/org/acme/model/dto/PublishScreenerRequest.java rename to builder-api/src/main/java/org/acme/model/dto/Screener/PublishScreenerRequest.java index d1bcdf2a..0f96d891 100644 --- a/builder-api/src/main/java/org/acme/model/dto/PublishScreenerRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/Screener/PublishScreenerRequest.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Screener; public class PublishScreenerRequest { public String screenerId; diff --git a/builder-api/src/main/java/org/acme/model/dto/SaveSchemaRequest.java b/builder-api/src/main/java/org/acme/model/dto/Screener/SaveSchemaRequest.java similarity index 87% rename from builder-api/src/main/java/org/acme/model/dto/SaveSchemaRequest.java rename to builder-api/src/main/java/org/acme/model/dto/Screener/SaveSchemaRequest.java index 8eacbb95..06fc4104 100644 --- a/builder-api/src/main/java/org/acme/model/dto/SaveSchemaRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/Screener/SaveSchemaRequest.java @@ -1,4 +1,4 @@ -package org.acme.model.dto; +package org.acme.model.dto.Screener; import com.fasterxml.jackson.databind.JsonNode; import org.acme.api.validation.HasSchema; diff --git a/builder-api/src/main/java/org/acme/persistence/EligibilityCheckRepository.java b/builder-api/src/main/java/org/acme/persistence/EligibilityCheckRepository.java index 87b5f253..ec4f9beb 100644 --- a/builder-api/src/main/java/org/acme/persistence/EligibilityCheckRepository.java +++ b/builder-api/src/main/java/org/acme/persistence/EligibilityCheckRepository.java @@ -8,12 +8,6 @@ public interface EligibilityCheckRepository { - List getPublicChecks(); - - Optional getPublicCheck(String checkId); - - List getChecksInBenefit(Benefit benefit); - List getWorkingCustomChecks(String userId); List getPublishedCheckVersions(EligibilityCheck workingCustomCheck); @@ -35,6 +29,4 @@ public interface EligibilityCheckRepository { void updateWorkingCustomCheck(EligibilityCheck check) throws Exception; void updatePublishedCustomCheck(EligibilityCheck check) throws Exception; - - String savePublicCheck(EligibilityCheck check) throws Exception; } diff --git a/builder-api/src/main/java/org/acme/persistence/impl/BenefitRepositoryImpl.java b/builder-api/src/main/java/org/acme/persistence/impl/BenefitRepositoryImpl.java deleted file mode 100644 index 537b9ec3..00000000 --- a/builder-api/src/main/java/org/acme/persistence/impl/BenefitRepositoryImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.acme.persistence.impl; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.enterprise.context.ApplicationScoped; -import org.acme.constants.CollectionNames; -import org.acme.constants.FieldNames; -import org.acme.model.domain.Benefit; -import org.acme.persistence.FirestoreUtils; -import org.acme.persistence.BenefitRepository; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@ApplicationScoped -public class BenefitRepositoryImpl implements BenefitRepository { - public List getAllBenefits() { - List> benefitMaps = FirestoreUtils.getFirestoreDocsByField( - CollectionNames.BENEFIT_COLLECTION, FieldNames.IS_PUBLIC, true); - - ObjectMapper mapper = new ObjectMapper(); - List benefits = benefitMaps.stream().map( benefitMap -> mapper.convertValue(benefitMap, Benefit.class)).toList(); - - return benefits; - } - - public Optional getBenefit(String benefitId) { - List> benefitMaps = FirestoreUtils.getFirestoreDocsByField( - CollectionNames.BENEFIT_COLLECTION, FieldNames.ID, benefitId); - - ObjectMapper mapper = new ObjectMapper(); - List benefits = benefitMaps.stream().map( benefitMap -> mapper.convertValue(benefitMap, Benefit.class)).toList(); - - if (benefits.isEmpty()) { - return Optional.empty(); - } else{ - return Optional.of(benefits.getFirst()); - } - } -} diff --git a/builder-api/src/main/java/org/acme/persistence/impl/EligibilityCheckRepositoryImpl.java b/builder-api/src/main/java/org/acme/persistence/impl/EligibilityCheckRepositoryImpl.java index 2e4eef73..9fdc2120 100644 --- a/builder-api/src/main/java/org/acme/persistence/impl/EligibilityCheckRepositoryImpl.java +++ b/builder-api/src/main/java/org/acme/persistence/impl/EligibilityCheckRepositoryImpl.java @@ -26,48 +26,6 @@ public class EligibilityCheckRepositoryImpl implements EligibilityCheckRepositor @Inject private StorageService storageService; - public List getPublicChecks(){ - - List> checkMaps = FirestoreUtils.getAllDocsInCollection( - CollectionNames.PUBLIC_CHECK_COLLECTION); - - ObjectMapper mapper = new ObjectMapper(); - List checks = checkMaps.stream().map( checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList(); - - return checks; } - - public Optional getPublicCheck(String checkId){ - Optional> dataOpt = FirestoreUtils.getFirestoreDocById(CollectionNames.PUBLIC_CHECK_COLLECTION, checkId); - if (dataOpt.isEmpty()){ - return Optional.empty(); - } - Map data = dataOpt.get(); - - ObjectMapper mapper = new ObjectMapper(); - EligibilityCheck check = mapper.convertValue(data, EligibilityCheck.class); - return Optional.of(check); - } - - public List getChecksInBenefit(Benefit benefit){ - List checkIds = benefit.getChecks().stream().map(checkConfig -> checkConfig.getCheckId()).toList(); - List> customCheckMaps = FirestoreUtils.getFirestoreDocsByIds( - CollectionNames.WORKING_CUSTOM_CHECK_COLLECTION, checkIds); - - // TODO: Replace with PUBLISHED_CUSTOM_CHECK_COLLECTION after implementing published checks - // List> customCheckMaps = FirestoreUtils.getFirestoreDocsByIds( - // CollectionNames.PUBLISHED_CUSTOM_CHECK_COLLECTION, checkIds); - - List> publicCheckMaps = FirestoreUtils.getFirestoreDocsByIds( - CollectionNames.PUBLIC_CHECK_COLLECTION, checkIds); - - List> checkMaps = new ArrayList<>(); - checkMaps.addAll(customCheckMaps); - checkMaps.addAll(publicCheckMaps); - - ObjectMapper mapper = new ObjectMapper(); - return checkMaps.stream().map(checkMap -> mapper.convertValue(checkMap, EligibilityCheck.class)).toList(); - } - public List getWorkingCustomChecks(String userId){ List> checkMaps = FirestoreUtils.getFirestoreDocsByField(CollectionNames.WORKING_CUSTOM_CHECK_COLLECTION, FieldNames.OWNER_ID, userId); ObjectMapper mapper = new ObjectMapper(); @@ -226,14 +184,6 @@ public void updatePublishedCustomCheck(EligibilityCheck check) throws Exception{ FirestoreUtils.updateDocument(CollectionNames.PUBLISHED_CUSTOM_CHECK_COLLECTION, data, check.getId()); } - public String savePublicCheck(EligibilityCheck check) throws Exception{ - String checkId = getPublishedId(check); - check.setId(checkId); - ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); - Map data = mapper.convertValue(check, Map.class); - return FirestoreUtils.persistDocumentWithId(CollectionNames.PUBLIC_CHECK_COLLECTION, checkId , data); - } - public String getWorkingId(EligibilityCheck check) { return CheckStatus.WORKING.getCode() + "-" + check.getOwnerId() + "-" + check.getModule() + "-" + check.getName(); } diff --git a/builder-api/src/main/java/org/acme/service/LibraryApiService.java b/builder-api/src/main/java/org/acme/service/LibraryApiService.java index d667ad1b..0009075c 100644 --- a/builder-api/src/main/java/org/acme/service/LibraryApiService.java +++ b/builder-api/src/main/java/org/acme/service/LibraryApiService.java @@ -90,14 +90,14 @@ public List getByModule(String module) { .toList(); } - public EligibilityCheck getById(String id) { + public Optional getById(String id) { List matches = checks.stream() .filter(e -> id.equals(e.getId())) .toList(); if (matches.isEmpty()) { - return null; + return Optional.empty(); } - return matches.getFirst(); + return Optional.of(matches.getFirst()); } public EvaluationResult evaluateCheck(CheckConfig checkConfig, Map inputs) throws JsonProcessingException { diff --git a/builder-api/src/test/java/org/acme/service/InputSchemaServiceTest.java b/builder-api/src/test/java/org/acme/service/InputSchemaServiceTest.java index 44e95025..f86873e5 100644 --- a/builder-api/src/test/java/org/acme/service/InputSchemaServiceTest.java +++ b/builder-api/src/test/java/org/acme/service/InputSchemaServiceTest.java @@ -619,7 +619,6 @@ void extractJsonSchemaPaths_withMultiplePersonIds_extractsPathsForAll() throws E JsonNode schema = objectMapper.readTree(schemaJson); List paths = service.extractJsonSchemaPaths(schema); - assertTrue(paths.contains(new FormPath("people.applicant.dateOfBirth", "date"))); assertTrue(paths.contains(new FormPath("people.spouse.dateOfBirth", "date"))); assertEquals(2, paths.size()); diff --git a/builder-frontend/src/api/benefit.ts b/builder-frontend/src/api/benefit.ts index f40a4841..a20deb2b 100644 --- a/builder-frontend/src/api/benefit.ts +++ b/builder-frontend/src/api/benefit.ts @@ -1,7 +1,8 @@ -import { authGet, authPut } from "@/api/auth"; +import { authDelete, authGet, authPatch, authPost } from "@/api/auth"; import { env } from "@/config/environment"; +import { authFetch } from "@/api/auth"; -import { Benefit } from "@/types"; +import { Benefit, UpdateCustomBenefitRequest, ParameterValues } from "@/types"; const apiUrl = env.apiUrl; @@ -26,11 +27,12 @@ export const fetchScreenerBenefit = async ( export const updateScreenerBenefit = async ( screenerId: string, - benefitData: Benefit, + benefitId: string, + benefitData: UpdateCustomBenefitRequest ): Promise => { - const url = apiUrl + "/screener/" + screenerId + "/benefit"; + const url = apiUrl + "/screener/" + screenerId + "/benefit/" + benefitId; try { - const response = await authPut(url, benefitData); + const response = await authPatch(url.toString(), benefitData); if (!response.ok) { throw new Error(`Update failed with status: ${response.status}`); @@ -38,23 +40,60 @@ export const updateScreenerBenefit = async ( const data = await response.json(); return data as Benefit; } catch (error) { - console.error("Error updating project:", error); + console.error("Error updating benefit:", error); throw error; } }; -export const fetchPublicBenefits = async (): Promise => { - const url = apiUrl + "/benefit"; +export const addCheckToBenefit = async ( + screenerId: string, + benefitId: string, + checkId: string +): Promise => { + const url = apiUrl + "/screener/" + screenerId + "/benefit/" + benefitId + "/check"; try { - const response = await authGet(url); + const response = await authPost(url.toString(), { checkId }); + if (!response.ok) { + throw new Error(`Add check failed with status: ${response.status}`); + } + } catch (error) { + console.error("Error adding check to benefit:", error); + throw error; + } +}; + +export const removeCheckFromBenefit = async ( + screenerId: string, + benefitId: string, + checkId: string +): Promise => { + const url = apiUrl + "/screener/" + screenerId + "/benefit/" + benefitId + "/check/" + checkId; + try { + const response = await authDelete(url); if (!response.ok) { - throw new Error(`Fetch failed with status: ${response.status}`); + throw new Error(`Remove check failed with status: ${response.status}`); } - const data = await response.json(); - return data; } catch (error) { - console.error("Error fetching benefits:", error); - throw error; // rethrow so you can handle it in your component if needed + console.error("Error removing check from benefit:", error); + throw error; + } +}; + +export const updateCheckParameters = async ( + screenerId: string, + benefitId: string, + checkId: string, + parameters: ParameterValues +): Promise => { + const url = apiUrl + "/screener/" + screenerId + "/benefit/" + benefitId + "/check/" + checkId + "/parameters"; + try { + const response = await authPatch(url.toString(), { parameters }) + if (!response.ok) { + throw new Error(`Update parameters failed with status: ${response.status}`); + } + } catch (error) { + console.error("Error updating check parameters:", error); + throw error; } }; diff --git a/builder-frontend/src/api/screener.ts b/builder-frontend/src/api/screener.ts index 2fb960a6..863fb067 100644 --- a/builder-frontend/src/api/screener.ts +++ b/builder-frontend/src/api/screener.ts @@ -2,7 +2,7 @@ import { env } from "@/config/environment"; import { authDelete, authGet, authPatch, authPost } from "@/api/auth"; -import type { BenefitDetail, FormPath, ScreenerResult } from "@/types"; +import type { CreateCustomBenefitRequest, FormPath, ScreenerResult } from "@/types"; const apiUrl = env.apiUrl; @@ -22,7 +22,7 @@ export const fetchProjects = async () => { } }; -export const fetchProject = async (screenerId) => { +export const fetchProject = async (screenerId: string) => { const url = apiUrl + "/screener/" + screenerId; try { const response = await authGet(url); @@ -76,8 +76,8 @@ export const updateScreener = async ( } }; -export const deleteScreener = async (screenerData) => { - const url = apiUrl + "/screener/delete?screenerId=" + screenerData.id; +export const deleteScreener = async (screenerId: string) => { + const url = apiUrl + "/screener/delete?screenerId=" + screenerId; try { const response = await authDelete(url); @@ -90,7 +90,7 @@ export const deleteScreener = async (screenerData) => { } }; -export const saveFormSchema = async (screenerId: string, schema) => { +export const saveFormSchema = async (screenerId: string, schema: any) => { const requestData: any = {}; requestData.schema = schema; const url = new URL(`${apiUrl}/save-form-schema`); @@ -122,10 +122,7 @@ export const publishScreener = async (screenerId: string): Promise => { } }; -export const addCustomBenefit = async ( - screenerId: string, - benefit: BenefitDetail, -) => { +export const addCustomBenefit = async (screenerId: string, benefit: CreateCustomBenefitRequest) => { const url = apiUrl + "/screener/" + screenerId + "/benefit"; try { const response = await authPost(url, benefit); diff --git a/builder-frontend/src/components/homeScreen/ProjectsList.tsx b/builder-frontend/src/components/homeScreen/ProjectsList.tsx index a8ed1e7d..ed9d08ec 100644 --- a/builder-frontend/src/components/homeScreen/ProjectsList.tsx +++ b/builder-frontend/src/components/homeScreen/ProjectsList.tsx @@ -66,7 +66,7 @@ export default function ProjectsList() { const handleDeleteScreener = async (screenerData) => { try { - await deleteScreener(screenerData); + await deleteScreener(screenerData.id); refetchProjectList(); setIsEditgModalVisible(false); } catch (e) { diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx b/builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx index 003a8dd5..267ad87a 100644 --- a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx +++ b/builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx @@ -1,6 +1,6 @@ import { createStore } from "solid-js/store" -import type { BenefitDetail } from "@/types"; +import type { CreateCustomBenefitRequest } from "@/types"; type NewBenefitValues = { @@ -9,7 +9,7 @@ type NewBenefitValues = { } const AddNewBenefitModal = ( { addNewBenefit, closeModal }: - { addNewBenefit: (benefit: BenefitDetail) => Promise; closeModal: () => void } + { addNewBenefit: (benefit: CreateCustomBenefitRequest) => Promise; closeModal: () => void } ) => { const [newBenefit, setNewBenefit] = createStore({ name: "", description: "" }); @@ -61,11 +61,9 @@ const AddNewBenefitModal = ( console.log("Please fill in all fields."); return; } - const benefitToAdd = { - id: crypto.randomUUID(), + const benefitToAdd: CreateCustomBenefitRequest = { name: newBenefit.name, description: newBenefit.description, - isPublic: false, }; await addNewBenefit(benefitToAdd); closeModal(); diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx b/builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx deleted file mode 100644 index 2a872e10..00000000 --- a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { createResource } from "solid-js"; - -import { fetchPublicBenefits } from "@/api/benefit"; - -import type { Benefit } from "@/types"; - - -const SelectExistingBenefitModal = ( - { copyPublicBenefit, closeModal }: - { copyPublicBenefit: (benefitId: string) => Promise; closeModal: () => void } -) => { - const [availableBenefits] = createResource(fetchPublicBenefits); - - return ( -
-
-
Copy from Existing Benefit
-
- {availableBenefits.loading && ( -
Loading available benefits...
- )} - {availableBenefits.error && ( -
- Error loading benefits: {availableBenefits.error.message} -
- )} - {availableBenefits() && availableBenefits().length === 0 && ( -
No available benefits found.
- )} - {availableBenefits() && availableBenefits().length > 0 && ( -
- {availableBenefits().map((benefit) => ( -
-
-
{benefit.name}
-
{benefit.description}
-
Eligibility Checks: {benefit.checks.length}
-
-
-
{ - await copyPublicBenefit(benefit.id); - closeModal(); - }} - > - Copy this benefit -
-
-
- ))} -
- )} -
-
-
{ - closeModal(); - }} - > - Close -
-
-
-
- ); -} -export default SelectExistingBenefitModal; diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts b/builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts index 78e2b8f2..f3b8eac8 100644 --- a/builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts +++ b/builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts @@ -7,13 +7,13 @@ import { removeCustomBenefit, } from "@/api/screener"; -import type { BenefitDetail, ScreenerBenefits } from "@/types"; +import type { BenefitDetail, CreateCustomBenefitRequest, ScreenerBenefits } from "@/types"; export interface ScreenerBenefitsResource { screenerBenefits: () => BenefitDetail[]; actions: { - addNewBenefit: (benefit: BenefitDetail) => Promise; + addNewBenefit: (benefit: CreateCustomBenefitRequest) => Promise; removeBenefit: (benefitIdToRemove: string) => Promise; // copyPublicBenefit: (benefitId: string) => Promise; }; @@ -41,7 +41,7 @@ const createScreenerBenefits = (screenerId: Accessor): ScreenerBenefitsR }); // Actions - const addNewBenefit = async (benefit: BenefitDetail) => { + const addNewBenefit = async (benefit: CreateCustomBenefitRequest) => { setActionInProgress(true); try { await addCustomBenefit(screenerId(), benefit); diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx b/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx index b3cc645c..969f1f25 100644 --- a/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx +++ b/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx @@ -29,8 +29,8 @@ const ConfigureBenefit = ({ fetchUserDefinedChecks(false) ); - const onRemoveEligibilityCheck = (checkIndexToRemove: number) => { - actions.removeCheck(checkIndexToRemove); + const onRemoveEligibilityCheck = (checkId: string) => { + actions.removeCheck(checkId); }; return ( @@ -84,19 +84,19 @@ const ConfigureBenefit = ({ )} {benefit().checks.length > 0 && ( - {(checkConfig, checkIndex) => { + {(checkConfig) => { return ( checkConfig.checkId} checkConfig={() => checkConfig} onRemove={() => - onRemoveEligibilityCheck(checkIndex()) + onRemoveEligibilityCheck(checkConfig.checkId) } updateCheckConfigParams={( newCheckData: ParameterValues ) => { actions.updateCheckConfigParams( - checkIndex(), + checkConfig.checkId, newCheckData ); }} diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx b/builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx index 859272db..d6ba8429 100644 --- a/builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx +++ b/builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx @@ -2,7 +2,7 @@ import { Accessor, For, Resource, Setter } from "solid-js"; import { titleCase } from "@/utils/title_case"; -import type { CheckConfig, EligibilityCheck } from "@/types"; +import type { EligibilityCheck } from "@/types"; export type EligibilityCheckListMode = "user-defined" | "public"; interface CheckModeConfig { @@ -41,7 +41,7 @@ const EligibilityCheckListView = ({ publicChecks, userDefinedChecks, }: { - addCheck: (newCheck: CheckConfig) => void; + addCheck: (checkId: string) => void; mode: Accessor; setMode: Setter; publicChecks: Resource; @@ -53,18 +53,8 @@ const EligibilityCheckListView = ({ mode() === "public" ? publicChecks : userDefinedChecks; const onAddEligibilityCheck = (check: EligibilityCheck) => { - const checkConfig: CheckConfig = { - checkId: check.id, - checkName: check.name, - checkVersion: check.version, - checkModule: check.module, - checkDescription: check.description, - evaluationUrl: check.evaluationUrl, - parameters: {}, - parameterDefinitions: check.parameterDefinitions, - inputDefinition: check.inputDefinition, - }; - addCheck(checkConfig); + // Only pass the checkId - the server will create the CheckConfig snapshot + addCheck(check.id); }; return ( diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts b/builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts index 09bed222..81ae458c 100644 --- a/builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts +++ b/builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts @@ -1,17 +1,22 @@ import { createResource, createEffect, Accessor, createSignal } from "solid-js"; import { createStore } from "solid-js/store"; -import { fetchScreenerBenefit, updateScreenerBenefit } from "@/api/benefit"; +import { + fetchScreenerBenefit, + addCheckToBenefit, + removeCheckFromBenefit, + updateCheckParameters +} from "@/api/benefit"; -import type { Benefit, CheckConfig, ParameterValues } from "@/types"; +import type { Benefit, ParameterValues } from "@/types"; interface ScreenerBenefitsResource { benefit: Accessor; actions: { - addCheck: (newCheck: CheckConfig) => void; - removeCheck: (indexToRemove: number) => void; + addCheck: (checkId: string) => void; + removeCheck: (checkId: string) => void; updateCheckConfigParams: ( - indexToUpdate: number, + checkId: string, parameters: ParameterValues ) => void; }; @@ -44,51 +49,47 @@ const createScreenerBenefits = ( } }); - // Optimistic update helper - const updateBenefit = async (newBenefit: Benefit) => { + // Actions + const addCheck = async (checkId: string) => { + if (!benefit) return; setActionInProgress(true); try { - await updateScreenerBenefit(screenerId(), { ...newBenefit }); + await addCheckToBenefit(screenerId(), benefitId(), checkId); await refetch(); } catch (e) { - console.error("Failed to update Benefit", e); + console.error("Failed to add check to benefit", e); } setActionInProgress(false); }; - // Actions - const addCheck = (newCheck: CheckConfig) => { - if (!benefit) return; - const updatedChecks: CheckConfig[] = [...benefit.checks, newCheck]; - const updatedBenefit: Benefit = { ...benefit, checks: updatedChecks }; - updateBenefit(updatedBenefit); - }; - const removeCheck = (indexToRemove: number) => { + const removeCheck = async (checkId: string) => { if (!benefit) return; + setActionInProgress(true); - const updatedChecks: CheckConfig[] = benefit.checks.filter( - (_, checkIndex) => checkIndex !== indexToRemove - ); - const updatedBenefit: Benefit = { ...benefit, checks: updatedChecks }; - updateBenefit(updatedBenefit); + try { + await removeCheckFromBenefit(screenerId(), benefitId(), checkId); + await refetch(); + } catch (e) { + console.error("Failed to remove check from benefit", e); + } + setActionInProgress(false); }; - const updateCheckConfigParams = ( - indexToUpdate: number, + + const updateCheckConfigParams = async ( + checkId: string, parameters: ParameterValues ) => { if (!benefit) return; + setActionInProgress(true); - const updatedCheckConfigs: CheckConfig[] = benefit.checks.map( - (check, checkIndex) => { - if (checkIndex === indexToUpdate) { - return { ...check, parameters: parameters }; - } - return check; - } - ); - const updatedBenefit: Benefit = { ...benefit, checks: updatedCheckConfigs }; - updateBenefit(updatedBenefit); + try { + await updateCheckParameters(screenerId(), benefitId(), checkId, parameters); + await refetch(); + } catch (e) { + console.error("Failed to update check parameters", e); + } + setActionInProgress(false); }; return { diff --git a/builder-frontend/src/types.ts b/builder-frontend/src/types.ts index eeb98c88..5a7c95d1 100644 --- a/builder-frontend/src/types.ts +++ b/builder-frontend/src/types.ts @@ -8,7 +8,6 @@ export interface BenefitDetail { id: string; name: string; description: string; - isPublic: boolean; } export interface Benefit { @@ -62,6 +61,25 @@ export interface UpdateCheckRequest { parameterDefinitions?: ParameterDefinition[]; } +// Request types for Custom Benefit API endpoints +export interface CreateCustomBenefitRequest { + name: string; + description: string; +} + +export interface UpdateCustomBenefitRequest { + name: string; + description: string; +} + +export interface AddCheckRequest { + checkId: string; +} + +export interface UpdateCheckParametersRequest { + parameters: ParameterValues; +} + // Parameter Types export type ParameterType = "string" | "number" | "boolean" | "date" | "array"; export type ParameterDefinition = From 031e0e8f3b177f1e09a8d79eedded4d1c7b340bb Mon Sep 17 00:00:00 2001 From: Justin-MacIntosh Date: Fri, 27 Feb 2026 13:08:10 -0500 Subject: [PATCH 2/2] chore: Added validation to DTOs used by CustomBenefit Resource. --- .../controller/CustomBenefitResource.java | 35 +++++++------------ .../dto/CustomBenefit/AddCheckRequest.java | 8 +++-- .../CreateCustomBenefitRequest.java | 10 +++--- .../UpdateCheckParametersRequest.java | 7 ++-- .../UpdateCustomBenefitRequest.java | 3 ++ 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java b/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java index 0dee07fb..e23658f1 100644 --- a/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java +++ b/builder-api/src/main/java/org/acme/controller/CustomBenefitResource.java @@ -3,6 +3,7 @@ import io.quarkus.logging.Log; import io.quarkus.security.identity.SecurityIdentity; import jakarta.inject.Inject; +import jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -74,29 +75,22 @@ public Response getCustomBenefits( public Response createCustomBenefit( @Context SecurityIdentity identity, @PathParam("screenerId") String screenerId, - CreateCustomBenefitRequest request + @Valid CreateCustomBenefitRequest request ) { String userId = AuthUtils.getUserId(identity); - // Validate request - if (request.name == null || request.name.isBlank()) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "Name is required")) - .build(); - } - // Create new Benefit with server-generated ID String newBenefitId = UUID.randomUUID().toString(); Benefit newBenefit = new Benefit( newBenefitId, - request.name, - request.description, + request.name(), + request.description(), userId, Collections.emptyList() ); // Create corresponding BenefitDetail - BenefitDetail benefitDetail = new BenefitDetail(newBenefitId, request.name, request.description); + BenefitDetail benefitDetail = new BenefitDetail(newBenefitId, request.name(), request.description()); try { // Check to make sure screener exists @@ -159,7 +153,7 @@ public Response updateCustomBenefit( @Context SecurityIdentity identity, @PathParam("screenerId") String screenerId, @PathParam("benefitId") String benefitId, - UpdateCustomBenefitRequest request + @Valid UpdateCustomBenefitRequest request ) { String userId = AuthUtils.getUserId(identity); @@ -271,17 +265,10 @@ public Response addCheckToBenefit( @Context SecurityIdentity identity, @PathParam("screenerId") String screenerId, @PathParam("benefitId") String benefitId, - AddCheckRequest request + @Valid AddCheckRequest request ) { String userId = AuthUtils.getUserId(identity); - // Validate request - if (request.checkId == null || request.checkId.isBlank()) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(Map.of("error", "checkId is required")) - .build(); - } - if (!isUserAuthorizedForScreener(userId, screenerId)) { return Response.status(Response.Status.UNAUTHORIZED).build(); } @@ -296,9 +283,11 @@ public Response addCheckToBenefit( } // Find the EligibilityCheck - first try user's custom checks, then library checks - Optional checkOpt = eligibilityCheckRepository.getPublishedCustomCheck(userId, request.checkId); + Optional checkOpt = ( + eligibilityCheckRepository.getPublishedCustomCheck(userId, request.checkId()) + ); if (checkOpt.isEmpty()) { - checkOpt = libraryApiMetadataService.getById(request.checkId); + checkOpt = libraryApiMetadataService.getById(request.checkId()); } if (checkOpt.isEmpty()) { @@ -440,7 +429,7 @@ public Response updateCheckParameters( List checkListAfterUpdate = new ArrayList<>(); for (CheckConfig check : checks) { if (check.getCheckId().equals(checkId)) { - check.setParameters(request.parameters != null ? request.parameters : new HashMap<>()); + check.setParameters(request.parameters() != null ? request.parameters() : new HashMap<>()); checkUpdated = true; } checkListAfterUpdate.add(check); diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java index 5bcc8cfd..5d7f9c0a 100644 --- a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/AddCheckRequest.java @@ -1,5 +1,7 @@ package org.acme.model.dto.CustomBenefit; -public class AddCheckRequest { - public String checkId; -} +import org.acme.api.validation.AtLeastOneProvided; + +@AtLeastOneProvided(fields = {"checkId"}) +public record AddCheckRequest(String checkId) {} + diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java index ac0f71dd..a70903fb 100644 --- a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/CreateCustomBenefitRequest.java @@ -1,6 +1,8 @@ package org.acme.model.dto.CustomBenefit; -public class CreateCustomBenefitRequest { - public String name; - public String description; -} +import jakarta.validation.constraints.NotBlank; + +public record CreateCustomBenefitRequest( + @NotBlank(message = "Custom Benefit name must be provided.") String name, + String description +) {} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java index 721904ba..89c6b60e 100644 --- a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCheckParametersRequest.java @@ -2,6 +2,7 @@ import java.util.Map; -public class UpdateCheckParametersRequest { - public Map parameters; -} +import org.acme.api.validation.AtLeastOneProvided; + +@AtLeastOneProvided(fields = {"parameters"}) +public record UpdateCheckParametersRequest(Map parameters) {} diff --git a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java index c136c92d..5fd202f7 100644 --- a/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java +++ b/builder-api/src/main/java/org/acme/model/dto/CustomBenefit/UpdateCustomBenefitRequest.java @@ -1,5 +1,8 @@ package org.acme.model.dto.CustomBenefit; +import org.acme.api.validation.AtLeastOneProvided; + +@AtLeastOneProvided(fields = {"name", "description"}) public class UpdateCustomBenefitRequest { public String name; public String description;