Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/5.0 #69

Merged
merged 11 commits into from
Nov 15, 2023
6 changes: 5 additions & 1 deletion .github/workflows/pushImage.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Push Image Workflow

on:
create:
push:
branches:
- release/**
Expand All @@ -15,7 +16,7 @@ jobs:
test:
runs-on: ubuntu-latest

# Need to check here as create event can't be filtered by branch name...
# Need to check here as create event can't be filtered by branch name: https://github.com/orgs/community/discussions/54860
if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release')

steps:
Expand Down Expand Up @@ -48,6 +49,9 @@ jobs:
path: ${{github.workspace}}/reports

build:
# Need to check here as create event can't be filtered by branch name: https://github.com/orgs/community/discussions/54860
if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release')

runs-on: ubuntu-latest

outputs:
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gov.cabinetoffice.gap.adminbackend.constants;

import java.util.Arrays;
import java.util.List;

public class SpotlightHeaders {

public static final List<String> SPOTLIGHT_HEADERS = Arrays.asList("Application number (required)",
"Organisation name (required)", "Address street (optional)", "Address town (optional)",
"Address county (optional)", "Address postcode (required)", "Application amount (required)",
"Charity Commission registration number (required - if applicable)",
"Companies House registration number (required - if applicable)",
"Similarities to other applications (optional)");

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import gov.cabinetoffice.gap.adminbackend.dtos.errors.GenericErrorDTO;
import gov.cabinetoffice.gap.adminbackend.dtos.schemes.SchemeDTO;
import gov.cabinetoffice.gap.adminbackend.entities.ApplicationFormEntity;
import gov.cabinetoffice.gap.adminbackend.entities.SchemeEntity;
import gov.cabinetoffice.gap.adminbackend.enums.ApplicationStatusEnum;
import gov.cabinetoffice.gap.adminbackend.exceptions.ApplicationFormException;
import gov.cabinetoffice.gap.adminbackend.exceptions.NotFoundException;
Expand All @@ -30,9 +29,7 @@
import javax.persistence.EntityNotFoundException;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

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

@Tag(name = "Application Forms", description = "API for handling organisations.")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gov.cabinetoffice.gap.adminbackend.controllers;

import gov.cabinetoffice.gap.adminbackend.services.FileService;
import gov.cabinetoffice.gap.adminbackend.services.GrantMandatoryQuestionService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;

import static gov.cabinetoffice.gap.adminbackend.controllers.SubmissionsController.EXPORT_CONTENT_TYPE;

@Log4j2
@RestController
@RequestMapping("/mandatory-questions")
@Tag(name = "Mandatory questions", description = "API for handling mandatory questions")
@RequiredArgsConstructor
public class GrantMandatoryQuestionsController {

final private GrantMandatoryQuestionService grantMandatoryQuestionService;

final private FileService fileService;

@GetMapping("/scheme/{schemeId}/complete")
public ResponseEntity<Boolean> hasCompletedMandatoryQuestions(@PathVariable Integer schemeId) {
return ResponseEntity.ok(grantMandatoryQuestionService.hasCompletedMandatoryQuestions(schemeId));
}

@GetMapping(value = "/spotlight-export/{schemeId}", produces = EXPORT_CONTENT_TYPE)
public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable Integer schemeId) {
log.info("Started mandatory questions export for scheme " + schemeId);
long start = System.currentTimeMillis();

final ByteArrayOutputStream stream = grantMandatoryQuestionService.exportSpotlightChecks(schemeId);
final String exportFileName = grantMandatoryQuestionService.generateExportFileName(schemeId);
final InputStreamResource resource = fileService.createTemporaryFile(stream, exportFileName);

final int length = stream.toByteArray().length;

// setting HTTP headers to tell caller we are returning a file
final HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.parse("attachment; filename=" + exportFileName));

long end = System.currentTimeMillis();
log.info("Finished mandatory questions export for scheme " + schemeId + ". Export time in millis: "
+ (end - start));

return ResponseEntity.ok().headers(headers).contentLength(length)
.contentType(MediaType.parseMediaType(EXPORT_CONTENT_TYPE)).body(resource);
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package gov.cabinetoffice.gap.adminbackend.controllers;

import gov.cabinetoffice.gap.adminbackend.config.UserServiceConfig;
import gov.cabinetoffice.gap.adminbackend.dtos.CheckNewAdminEmailDto;
import gov.cabinetoffice.gap.adminbackend.dtos.schemes.SchemeDTO;
import gov.cabinetoffice.gap.adminbackend.dtos.schemes.SchemePatchDTO;
import gov.cabinetoffice.gap.adminbackend.dtos.schemes.SchemePostDTO;
import gov.cabinetoffice.gap.adminbackend.entities.GrantAdmin;
import gov.cabinetoffice.gap.adminbackend.services.ApplicationFormService;
import gov.cabinetoffice.gap.adminbackend.services.GrantAdvertService;
import gov.cabinetoffice.gap.adminbackend.services.SchemeService;
import gov.cabinetoffice.gap.adminbackend.services.UserService;
import gov.cabinetoffice.gap.adminbackend.utils.HelperUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
Expand All @@ -20,14 +27,18 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Tag(name = "Schemes", description = "API for handling grant schemes.")
@RequestMapping("/schemes")
Expand All @@ -38,6 +49,14 @@ public class SchemeController {

private final SchemeService schemeService;

private final GrantAdvertService grantAdvertService;

private final UserService userService;

private final ApplicationFormService applicationFormService;

private final UserServiceConfig userServiceConfig;

@GetMapping("/{schemeId}")
@Operation(summary = "Retrieve grant scheme which matches the given id.")
@ApiResponses(value = {
Expand Down Expand Up @@ -167,11 +186,31 @@ public ResponseEntity<List<SchemeDTO>> getAllSchemes(final @RequestParam(default
}
}

@GetMapping("/admin/{adminId}")
public ResponseEntity<List<SchemeDTO>> getAdminsSchemes(final @PathVariable Integer adminId,
@PatchMapping("/{schemeId}/scheme-ownership")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@Transactional
public ResponseEntity<String> updateGrantOwnership(@PathVariable final Integer schemeId,
@RequestBody final CheckNewAdminEmailDto checkNewAdminEmailDto, final HttpServletRequest request) {
final String jwt = HelperUtils.getJwtFromCookies(request, userServiceConfig.getCookieName());
int grantAdminId = userService.getGrantAdminIdFromUserServiceEmail(checkNewAdminEmailDto.getEmailAddress(),
jwt);
schemeService.patchCreatedBy(grantAdminId, schemeId);
grantAdvertService.patchCreatedBy(grantAdminId, schemeId);
applicationFormService.patchCreatedBy(grantAdminId, schemeId);
return ResponseEntity.ok("Grant ownership updated successfully");
}

@GetMapping("/admin/{sub}")
@PreAuthorize("hasRole('SUPER_ADMIN')")
public ResponseEntity<List<SchemeDTO>> getAdminsSchemes(final @PathVariable String sub,
final HttpServletRequest request) {
List<SchemeDTO> schemes = this.schemeService.getAdminsSchemes(adminId);
return ResponseEntity.ok().body(schemes);
final Optional<GrantAdmin> grantAdmin = userService.getGrantAdminIdFromSub(sub);
if (grantAdmin.isPresent()) {
final Integer adminId = grantAdmin.get().getId();
List<SchemeDTO> schemes = this.schemeService.getAdminsSchemes(adminId);
return ResponseEntity.ok().body(schemes);
}
return ResponseEntity.ok().body(Collections.emptyList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import gov.cabinetoffice.gap.adminbackend.dtos.submission.SubmissionExportsDTO;
import gov.cabinetoffice.gap.adminbackend.enums.GrantExportStatus;
import gov.cabinetoffice.gap.adminbackend.exceptions.NotFoundException;
import gov.cabinetoffice.gap.adminbackend.services.FileService;
import gov.cabinetoffice.gap.adminbackend.services.SecretAuthService;
import gov.cabinetoffice.gap.adminbackend.services.SubmissionsService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -38,14 +39,16 @@ public class SubmissionsController {

private final SecretAuthService secretAuthService;

private final FileService fileService;

@GetMapping(value = "/spotlight-export/{applicationId}", produces = EXPORT_CONTENT_TYPE)
public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable Integer applicationId) {
log.info("Started submissions export for application " + applicationId);
long start = System.currentTimeMillis();

final ByteArrayOutputStream stream = submissionsService.exportSpotlightChecks(applicationId);
final String exportFileName = submissionsService.generateExportFileName(applicationId);
final InputStreamResource resource = createTemporaryFile(stream, exportFileName);
final InputStreamResource resource = fileService.createTemporaryFile(stream, exportFileName);

submissionsService.updateSubmissionLastRequiredChecksExport(applicationId);

Expand All @@ -65,20 +68,6 @@ public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable I
.contentType(MediaType.parseMediaType(EXPORT_CONTENT_TYPE)).body(resource);
}

private InputStreamResource createTemporaryFile(ByteArrayOutputStream stream, String filename) {
try {
File tempFile = File.createTempFile(filename, ".xlsx");
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile))) {
stream.writeTo(out);
}
return new InputStreamResource(new ByteArrayInputStream(stream.toByteArray()));
}
catch (Exception e) {
log.error("Error creating temporary for file {} problem reported {}", filename, e.getMessage());
throw new RuntimeException(e);
}
}

@PostMapping("/export-all/{applicationId}")
public ResponseEntity exportAllSubmissions(@PathVariable Integer applicationId) {
submissionsService.triggerSubmissionsExport(applicationId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gov.cabinetoffice.gap.adminbackend.controllers;

import com.auth0.jwt.interfaces.DecodedJWT;
import gov.cabinetoffice.gap.adminbackend.config.UserServiceConfig;
import gov.cabinetoffice.gap.adminbackend.dtos.CheckNewAdminEmailDto;
import gov.cabinetoffice.gap.adminbackend.dtos.MigrateUserDto;
import gov.cabinetoffice.gap.adminbackend.dtos.UserDTO;
import gov.cabinetoffice.gap.adminbackend.exceptions.FieldViolationException;
import gov.cabinetoffice.gap.adminbackend.mappers.UserMapper;
import gov.cabinetoffice.gap.adminbackend.models.AdminSession;
import gov.cabinetoffice.gap.adminbackend.models.JwtPayload;
Expand All @@ -13,10 +16,13 @@
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
Expand All @@ -35,6 +41,8 @@ public class UserController {

private final UserService userService;

private final UserServiceConfig userServiceConfig;

@Value("${feature.onelogin.enabled}")
private boolean oneLoginEnabled;

Expand Down Expand Up @@ -95,4 +103,23 @@ public ResponseEntity<String> deleteUser(@PathVariable Optional<String> oneLogin
return ResponseEntity.ok("User deleted successfully");
}

@PostMapping(value = "/validate-admin-email")
@PreAuthorize("hasRole('SUPER_ADMIN')")
public ResponseEntity checkNewAdminEmailIsValid(
@RequestBody @Valid final CheckNewAdminEmailDto checkNewAdminEmailDto, final HttpServletRequest request) {
final String jwt = HelperUtils.getJwtFromCookies(request, userServiceConfig.getCookieName());

if (checkNewAdminEmailDto.getEmailAddress().equals(checkNewAdminEmailDto.getOldEmailAddress())) {
throw new FieldViolationException("emailAddress", "This user already owns this grant.");
}

try {
userService.getGrantAdminIdFromUserServiceEmail(checkNewAdminEmailDto.getEmailAddress(), jwt);
}
catch (Exception e) {
throw new FieldViolationException("emailAddress", "Email address does not belong to an admin user");
}
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gov.cabinetoffice.gap.adminbackend.dtos;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CheckNewAdminEmailDto {

@Email(message = "Please enter a valid email address")
@NotBlank(message = "Please enter an email address")
private String emailAddress;

private String oldEmailAddress;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gov.cabinetoffice.gap.adminbackend.dtos;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GrantMandatoryQuestionDto {

private UUID id;

private int schemeId;

private UUID submissionId;

private String name;

private String addressLine1;

private String addressLine2;

private String city;

private String county;

private String postcode;

private String charityCommissionNumber;

private String companiesHouseNumber;

private String orgType;

private String fundingAmount;

private List<String> fundingLocation;

}
Loading