Skip to content

Commit

Permalink
Merge pull request #292 from bcgov/feature/GRAD2-3037
Browse files Browse the repository at this point in the history
Feature/grad2 3037
  • Loading branch information
mightycox authored Dec 23, 2024
2 parents eee5750 + 7356b82 commit d4b6e4c
Show file tree
Hide file tree
Showing 57 changed files with 3,166 additions and 93 deletions.
7 changes: 6 additions & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,16 @@
<artifactId>resilience4j-annotations</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ca.bc.gov.educ.api.grad.report.config;

import ca.bc.gov.educ.api.grad.report.exception.EntityNotFoundException;
import ca.bc.gov.educ.api.grad.report.exception.GradBusinessRuleException;
import ca.bc.gov.educ.api.grad.report.util.ApiResponseMessage;
import ca.bc.gov.educ.api.grad.report.util.ApiResponseModel;
Expand Down Expand Up @@ -38,11 +39,15 @@ protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest
return new ResponseEntity<>(reponse, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class })
@ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class, EntityNotFoundException.class })
protected ResponseEntity<Object> handleEntityNotFound(RuntimeException ex, WebRequest request) {
log.error("JPA ERROR IS: " + ex.getClass().getName(), ex);
// no need to log EntityNotFoundExceptions as error
// it is used to denote NOT FOUND and is normal part of operations
if(!(ex instanceof EntityNotFoundException)) {
log.error("JPA ERROR IS: " + ex.getClass().getName(), ex);
}
validation.clear();
return new ResponseEntity<>(ApiResponseModel.ERROR(null, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(ApiResponseModel.ERROR(null, ex.getLocalizedMessage()), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(value = { AccessDeniedException.class })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,65 @@

import ca.bc.gov.educ.api.grad.report.util.EducGradReportApiConstants;
import ca.bc.gov.educ.api.grad.report.util.LogHelper;
import io.netty.handler.logging.LogLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

import java.time.Duration;

@Configuration
@Profile("!test")
public class RestWebClient {
@Autowired
EducGradReportApiConstants constants;

private final HttpClient httpClient;

public RestWebClient() {
this.httpClient = HttpClient.create(ConnectionProvider.create("student-api")).compress(true)
.resolver(spec -> spec.queryTimeout(Duration.ofMillis(200)).trace("DNS", LogLevel.TRACE));
this.httpClient.warmup().block();
@Autowired
public RestWebClient(EducGradReportApiConstants constants) {
this.constants = constants;
}
@Bean("graduationReportClient")
public WebClient getGraduationReportClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
filter.setDefaultClientRegistrationId("graduation-report-client");
return WebClient.builder()
.exchangeStrategies(ExchangeStrategies
.builder()
.codecs(codecs -> codecs
.defaultCodecs()
.maxInMemorySize(50 * 1024 * 1024))
.build())
.apply(filter.oauth2Configuration())
.filter(this.log())
.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}

@Bean
public WebClient webClient() {
DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory();
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
return WebClient.builder().uriBuilderFactory(defaultUriBuilderFactory).exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(300 * 1024 * 1024)) // 300 MB
.build())
return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(300 * 1024 * 1024)) // 300MB
.build())
.filter(this.log())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ca.bc.gov.educ.api.grad.report.constants;

import lombok.Getter;

import java.util.Arrays;
import java.util.Optional;

@Getter
public enum GradReportTypesEnum {
TVRGRAD("TVRGRAD", "TVRs for Projected Graduating Students"),
TVRNONGRAD("TVRNONGRAD", "TVRs for Projected Non-Graduating Students"),
GRADREGARC("GRADREGARC", "Archived Graduated Students (MM YYYY to MM YYYY) Report"),
NONGRADREGARC("NONGRADREGARC", "Archived Not-Yet Graduated Students (MM YYYY to MM YYYY) Report"),
NONGRADPRJARC("NONGRADPRJARC", "Archived Projected Non-Graduates - Summary Report (MM YYYY to MM YYYY)"),
NONGRADPRJ("NONGRADPRJ", "Projected Non-Graduates - Summary Report (MM YYYY to MM YYYY)"),
ACHV("ACHV", "Transcript Verification Report"),
GRADREG("GRADREG", "Graduated Students (MM YYYY to MM YYYY) Report"),
DISTREP_SC("DISTREP_SC", "Credentials and Transcript Distribution Report"),
DISTREP_YE_SC("DISTREP_YE_SC", "Year-End Credentials and Transcript Distribution Report (School)"),
DISTREP_YE_SD("DISTREP_YE_SD", "Year-End District Credentials and Transcript Distribution Report (District)"),
NONGRADDISTREP_SC("NONGRADDISTREP_SC", "Non-Graduation Transcript Distribution Report (School)"),
NONGRADDISTREP_SD("NONGRADDISTREP_SD", "Non-Graduation Transcript Distribution Report (District)"),
GRADPRJ("GRADPRJ", "Projected Graduates - Summary Report (MM YYYY to MM YYYY)"),
GRADPRJARC("GRADPRJARC", "Archived Projected Graduates - Summary Report (MM YYYY to MM YYYY)"),
ADDRESS_LABEL_SCHL("ADDRESS_LABEL_SCHL", "Mailing Labels - School"),
ADDRESS_LABEL_YE("ADDRESS_LABEL_YE", "Year-End Mailing Labels - School and District"),
ADDRESS_LABEL_PSI("ADDRESS_LABEL_PSI", "Year-End Mailing Labels - PSIs"),
NONGRADREG("NONGRADREG", "Not-Yet Graduated Students (MM YYYY to MM YYYY) Report"),
XML("XML", "XML Preview"),
TRAN("TRAN", "Student Transcript"),
ADDRESS_LABEL("ADDRESS_LABEL", "Mailing Labels - School and District"),
DISTREP_SD("DISTREP_SD", "District Credentials and Transcript Distribution Report (District)");

private final String code;
private final String label;

GradReportTypesEnum(String code, String label) {
this.code = code;
this.label = label;
}

public static Optional<GradReportTypesEnum> findByValue(String value) {
return Arrays.stream(values()).filter(e -> Arrays.asList(e.code).contains(value)).findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package ca.bc.gov.educ.api.grad.report.controller.v2;

import ca.bc.gov.educ.api.grad.report.model.dto.StudentCredentialDistribution;
import ca.bc.gov.educ.api.grad.report.model.dto.v2.StudentSearchRequest;

import ca.bc.gov.educ.api.grad.report.service.v2.CommonService;
import ca.bc.gov.educ.api.grad.report.util.*;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController("commonControllerV2")
@RequestMapping(EducGradReportApiConstants.GRAD_REPORT_API_V2_ROOT_MAPPING)
@OpenAPIDefinition(info = @Info(title = "API for Common v2 endpoints.", description = "This API is for Reading Common endpoints.", version = "2"), security = {@SecurityRequirement(name = "OAUTH2", scopes = {"READ_GRAD_STUDENT_CERTIFICATE_DATA"})})
public class CommonController {

private static final Logger logger = LoggerFactory.getLogger(CommonController.class);

private static final String BEARER = "Bearer ";
final CommonService commonService;

final GradValidation validation;

final ResponseHelper response;

@Autowired
public CommonController(CommonService commonService, GradValidation validation, ResponseHelper response) {
this.commonService = commonService;
this.validation = validation;
this.response = response;
}

@PostMapping(EducGradReportApiConstants.USER_REQUEST_DIS_RUN)
@PreAuthorize(PermissionsConstants.READ_GRADUATION_STUDENT_CERTIFICATES)
@Operation(summary = "Read All Student Transcripts/Certificates for User Req Distribution", description = "Read All Student Credentials for Distribution", tags = { "Certificates" })
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
public ResponseEntity<List<StudentCredentialDistribution>> getStudentCredentialsForUserRequestDisRun(
@PathVariable String credentialType, @RequestBody StudentSearchRequest studentSearchRequest,
@RequestHeader(name="Authorization") String accessToken) {
logger.debug("getStudentCredentialsForUserRequestDisRun : ");
boolean isPenNumberSearch = studentSearchRequest.getPens() != null && !studentSearchRequest.getPens().isEmpty()
&& !studentSearchRequest.getPens().stream().filter(StringUtils::isNotBlank).toList().isEmpty();
boolean onlyWithNullDistributionDate = !isPenNumberSearch && studentSearchRequest.getGradDateFrom() == null && studentSearchRequest.getGradDateTo() == null && !StringUtils.equalsAnyIgnoreCase(credentialType, "OT", "RT");
return response.GET(commonService.getStudentCredentialsForUserRequestDisRun(credentialType,studentSearchRequest,onlyWithNullDistributionDate,accessToken.replace(BEARER, "")));
}

@PostMapping(EducGradReportApiConstants.USER_REQUEST_DIS_RUN_WITH_NULL_DISTRIBUTION_DATE)
@PreAuthorize(PermissionsConstants.READ_GRADUATION_STUDENT_CERTIFICATES)
@Operation(summary = "Read All Student Transcripts/Certificates with Null Distribution Date for User Req Distribution", description = "Read All Student Credentials with Null Distribution Date for Distribution", tags = { "Certificates" })
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
public ResponseEntity<List<StudentCredentialDistribution>> getStudentCredentialsForUserRequestDisRunWithNullDistributionDate(
@PathVariable String credentialType, @RequestBody StudentSearchRequest studentSearchRequest,
@RequestHeader(name="Authorization") String accessToken) {
logger.debug("getStudentCredentialsForUserRequestDisRunWithNullDistributionDate : ");
boolean isPenNumberSearch = studentSearchRequest.getPens()!= null && !studentSearchRequest.getPens().isEmpty()
&& !studentSearchRequest.getPens().stream().filter(StringUtils::isNotBlank).toList().isEmpty();
return response.GET(commonService.getStudentCredentialsForUserRequestDisRun(credentialType,studentSearchRequest,!isPenNumberSearch,accessToken.replace(BEARER, "")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package ca.bc.gov.educ.api.grad.report.controller.v2;

import ca.bc.gov.educ.api.grad.report.model.dto.v2.reports.DistrictReport;
import ca.bc.gov.educ.api.grad.report.service.v2.DistrictReportService;
import ca.bc.gov.educ.api.grad.report.util.EducGradReportApiConstants;
import ca.bc.gov.educ.api.grad.report.util.GradValidation;
import ca.bc.gov.educ.api.grad.report.util.PermissionsConstants;
import ca.bc.gov.educ.api.grad.report.util.ResponseHelper;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping(EducGradReportApiConstants.DISTRICT_REPORTS_ROOT_MAPPING)
@OpenAPIDefinition(info = @Info(title = "API for District Reports endpoints.", description = "This API is for reading and updating endpoints.", version = "2"), security = {@SecurityRequirement(name = "OAUTH2", scopes = {"READ_GRAD_STUDENT_REPORT_DATA","UPDATE_GRAD_STUDENT_REPORT_DATA"})})
public class DistrictReportController {
private static final Logger logger = LoggerFactory.getLogger(DistrictReportController.class);

DistrictReportService service;
GradValidation validation;
ResponseHelper response;

@Autowired
public DistrictReportController(DistrictReportService service, GradValidation validation, ResponseHelper response) {
this.service = service;
this.validation = validation;
this.response = response;
}

@GetMapping(EducGradReportApiConstants.SEARCH_MAPPING)
@PreAuthorize(PermissionsConstants.READ_GRADUATION_STUDENT_REPORTS)
@Operation(summary = "Search District Reports", description = "Search for district reports with optional filters", tags = {"Reports"})
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "Bad request")})
public ResponseEntity<List<DistrictReport>> searchDistrictReports(
@RequestParam(value = "districtId", required = false) UUID districtId,
@RequestParam(value = "reportTypeCode", required = false) String reportTypeCode,
@RequestParam(value = "isLight", defaultValue = "false") boolean isLight) {
logger.debug("searchDistrictReports: ");
List<DistrictReport> reports = service.searchDistrictReports(districtId, reportTypeCode, isLight);
return ResponseEntity.ok(reports);
}

@GetMapping()
@PreAuthorize(PermissionsConstants.READ_GRADUATION_STUDENT_REPORTS)
@Operation(summary = "Read district report data by district id and report type code", description = "Read district report data by district id and report type code", tags = {"Reports"})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
public ResponseEntity<InputStreamResource> getDistrictReportByDistrictIdAndReportType(
@RequestParam(value = "districtId") UUID districtId,
@RequestParam(value = "reportTypeCode") String reportTypeCode) {
logger.debug("getDistrictReportByType v2: ");
var stream = service.getDistrictReportByDistrictIdAndReportType(districtId, reportTypeCode);
return ResponseEntity.ok(stream);
}

@PostMapping()
@PreAuthorize(PermissionsConstants.UPDATE_GRADUATION_STUDENT_REPORTS)
@Operation(summary = "Save District Reports", description = "Save District Reports", tags = { "Reports" })
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "Bad request")})
public DistrictReport saveDistrictReport(@RequestBody DistrictReport districtReports) {
logger.debug("Save {} District Report for {}",districtReports.getReportTypeCode(),districtReports.getDistrictId());
validation.requiredField(districtReports.getDistrictId(), "District Id");
return service.saveDistrictReports(districtReports);
}

@DeleteMapping(EducGradReportApiConstants.DELETE_DISTRICT_REPORT)
@PreAuthorize(PermissionsConstants.UPDATE_GRADUATION_STUDENT_REPORTS)
@Operation(summary = "Delete district report: ", description = "Delete a specific district report by districtId and reportTypeCode", tags = {"Credential"})
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Deleted successfully"),
@ApiResponse(responseCode = "400", description = "Bad request")})
public ResponseEntity<Void> deleteDistrictReport(@PathVariable @NotNull UUID districtId, @PathVariable @NotNull String reportTypeCode) {
logger.debug("deleteDistrictReport: ");
service.deleteDistrictReport(districtId, StringUtils.trim(reportTypeCode));
return ResponseEntity.noContent().build();
}

@DeleteMapping
@PreAuthorize(PermissionsConstants.UPDATE_GRADUATION_STUDENT_REPORTS)
@Operation(summary = "Delete district reports by type", description = "Delete all district reports with the specified reportTypeCode", tags = {"Credential"})
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Deleted successfully"),
@ApiResponse(responseCode = "400", description = "Bad request")})
public ResponseEntity<Void> deleteDistrictReportsByType(@RequestParam @NotNull String reportTypeCode) {
logger.debug("deleteDistrictReportsByType: ");
service.deleteAllDistrictReportsByType(StringUtils.trim(reportTypeCode));
return ResponseEntity.noContent().build();
}
}
Loading

0 comments on commit d4b6e4c

Please sign in to comment.