Skip to content

Commit

Permalink
Merge branch 'main' into fix_background_color
Browse files Browse the repository at this point in the history
  • Loading branch information
bseeger authored Dec 15, 2023
2 parents 11bc1c9 + 0fb2099 commit 8fcfdf9
Show file tree
Hide file tree
Showing 132 changed files with 8,229 additions and 868 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v3.13.0
uses: actions/setup-java@v4.0.0
with:
distribution: 'adopt'
java-version: '17'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
with:
chromedriver-version: '115.0.5790.102'
- name: Set up JDK
uses: actions/setup-java@v3.13.0
uses: actions/setup-java@v4.0.0
with:
distribution: 'adopt'
java-version: '17'
Expand Down Expand Up @@ -83,7 +83,7 @@ jobs:
with:
chromedriver-version: '115.0.5790.102'
- name: Set up JDK
uses: actions/setup-java@v3.13.0
uses: actions/setup-java@v4.0.0
with:
distribution: 'adopt'
java-version: '17'
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ out/
.vscode/

### Mac OS ###
.DS_Store
.DS_Store

*.log
30 changes: 23 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,21 @@ repositories {
}

def profile = props.getProperty('SPRING_PROFILES_ACTIVE')
def formFlowLibraryVersion = '0.0.15'
def formFlowLibraryVersion = '1.0.0'
def useLocalLibrary = System.getenv('USE_LOCAL_LIBRARY')

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.jcraft:jsch:0.1.55'
implementation 'org.springframework.shell:spring-shell-starter:3.1.6'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation("com.mixpanel:mixpanel-java:1.5.2")
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.11'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
implementation group: 'ch.qos.logback.contrib', name: 'logback-json-classic', version: '0.1.5'
implementation group: 'ch.qos.logback.contrib', name: 'logback-jackson', version: '0.1.5'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.0'
implementation 'com.opencsv:opencsv:5.9'

if (profile == 'dev' || useLocalLibrary == 'true') {
implementation fileTree(dir: "$rootDir/../form-flow/build/libs", include: '*.jar')
Expand All @@ -49,12 +52,19 @@ dependencies {
println "📚Using form flow library ${formFlowLibraryVersion}"
}

implementation 'com.amazonaws:aws-encryption-sdk-java:2.4.1'
implementation 'com.amazonaws:aws-encryption-sdk-java:3.0.0'
implementation 'org.bouncycastle:bcpg-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'org.springframework.shell:spring-shell-starter:3.0.4'

implementation 'commons-net:commons-net:3.10.0'
testImplementation 'org.projectlombok:lombok:1.18.30'

implementation 'io.sentry:sentry-spring-boot-starter-jakarta:6.34.0'
implementation 'io.sentry:sentry-spring-boot-starter-jakarta:7.0.0'
implementation 'io.sentry:sentry-logback:7.0.0'

testImplementation 'junit:junit:4.13.2'

implementation 'io.sentry:sentry-logback:6.34.0'

compileOnly 'org.projectlombok:lombok'

Expand All @@ -64,8 +74,8 @@ dependencies {

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
testImplementation 'org.seleniumhq.selenium:selenium-java:4.15.0'
testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.3'
testImplementation 'org.seleniumhq.selenium:selenium-java:4.16.1'
testImplementation 'io.percy:percy-java-selenium:2.0.2'
testImplementation 'org.awaitility:awaitility'
testImplementation 'io.github.bonigarcia:webdrivermanager:5.6.2'
Expand All @@ -81,6 +91,12 @@ springBoot {
buildInfo()
}

dependencyManagement {
imports {
mavenBom "org.springframework.shell:spring-shell-dependencies:3.1.6"
}
}

test {
useJUnitPlatform {
JUnitPlatformOptions options ->
Expand Down
22 changes: 22 additions & 0 deletions scripts/generate_migration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
set -euo pipefail

migrations_path="$(dirname $0)/../src/main/resources/db/migration"

if [ ! -d "$migrations_path" ]; then
echo "Migration directory '$migrations_path' does not exist. Creating..."
mkdir -p "$migrations_path"
fi

migrations_path=$(realpath "$migrations_path")

printf "Generating migration file. \nEnter Description (e.g. 'Create admin users'): "
read description

filename="V$(date +%Y.%m.%d.%H.%M.%S)__$(echo "$description" | sed -E 's/[^A-Z]+/_/ig').sql"
echo "Creating ${filename}."
touch "$migrations_path/$filename"

echo "Hit enter to open."
read
open -a "IntelliJ IDEA.app" "$migrations_path/$filename"
157 changes: 157 additions & 0 deletions src/main/java/org/ladocuploader/app/FileExportController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package org.ladocuploader.app;



import com.opencsv.exceptions.CsvDataTypeMismatchException;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;


import formflow.library.config.FlowConfiguration;
import formflow.library.data.Submission;
import formflow.library.data.SubmissionRepositoryService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;


import java.io.IOException;
import java.util.*;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.StringEscapeUtils;
import org.ladocuploader.app.csv.CsvDocument;
import org.ladocuploader.app.csv.CsvService;
import org.ladocuploader.app.csv.enums.CsvType;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
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.server.ResponseStatusException;

import static formflow.library.FormFlowController.getSubmissionIdForFlow;

@Controller
@EnableAutoConfiguration
@Slf4j
@RequestMapping("/download-csv")
public class FileExportController {

private final MessageSource messageSource;

private final SubmissionRepositoryService submissionRepositoryService;

private final List<FlowConfiguration> flowConfigurations;

private final CsvService csvService;

public FileExportController(MessageSource messageSource,
SubmissionRepositoryService submissionRepositoryService,
List<FlowConfiguration> flowConfigurations, CsvService csvService) {

this.submissionRepositoryService = submissionRepositoryService;
this.flowConfigurations = flowConfigurations;
this.messageSource = messageSource;
this.csvService = csvService;
}


@GetMapping("{flow}/pg/{submissionId}")
ResponseEntity<?> downloadPGCsv(
@PathVariable String flow,
@PathVariable String submissionId,
HttpSession httpSession,
HttpServletRequest request,
Locale locale
) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {

String cleanedFlow = StringEscapeUtils.escapeHtml4(flow);
String cleanedSubmissionId = StringEscapeUtils.escapeHtml4(submissionId);

log.info("GET downloadCSV ParentGuardian (url: {}): flow: {}, submissionId: {}", request.getRequestURI().toLowerCase(),
cleanedFlow, cleanedSubmissionId);

return handleCsvGeneration(cleanedFlow, cleanedSubmissionId, httpSession, locale, CsvType.PARENT_GUARDIAN);
}

@GetMapping("{flow}/student/{submissionId}")
ResponseEntity<?> downloadStudentCsv(
@PathVariable String flow,
@PathVariable String submissionId,
HttpSession httpSession,
HttpServletRequest request,
Locale locale
) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {

String cleanedFlow = StringEscapeUtils.escapeHtml4(flow);
String cleanedSubmissionId = StringEscapeUtils.escapeHtml4(submissionId);

log.info("GET downloadCSV Student (url: {}): flow: {}, submissionId: {}", request.getRequestURI().toLowerCase(),
cleanedFlow, cleanedSubmissionId);

return handleCsvGeneration(cleanedFlow, cleanedSubmissionId, httpSession, locale, CsvType.STUDENT);
}

@GetMapping("{flow}/rel/{submissionId}")
ResponseEntity<?> downloadRelCsv(
@PathVariable String flow,
@PathVariable String submissionId,
HttpSession httpSession,
HttpServletRequest request,
Locale locale
) throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {

String cleanedFlow = StringEscapeUtils.escapeHtml4(flow);
String cleanedSubmissionId = StringEscapeUtils.escapeHtml4(submissionId);

log.info("GET downloadCSV Relationship (url: {}): flow: {}, submissionId: {}", request.getRequestURI().toLowerCase(),
cleanedFlow, cleanedSubmissionId);

return handleCsvGeneration(cleanedFlow, cleanedSubmissionId, httpSession, locale, CsvType.RELATIONSHIP);
}

protected static void throwNotFoundError(String flow, String message) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
String.format("There was a problem with the request during CSV Generation (flow: %s): %s",
flow, message));

}

protected Boolean doesFlowExist(String flow) {
return flowConfigurations.stream().anyMatch(
flowConfiguration -> flowConfiguration.getName().equals(flow)
);
}

private ResponseEntity handleCsvGeneration(String flow, String submissionId, HttpSession httpSession,
Locale locale, CsvType csvType) throws CsvRequiredFieldEmptyException, CsvDataTypeMismatchException, IOException {

if (!doesFlowExist(flow)) {
throwNotFoundError(flow, String.format("Could not find flow %s in your application's flow configuration.", flow));
}
// TODO: get list of submissions based on another column - like transmission?
Optional<Submission> maybeSubmission = submissionRepositoryService.findById(UUID.fromString(submissionId));
if (getSubmissionIdForFlow(httpSession, flow).toString().equals(submissionId) && maybeSubmission.isPresent()) {
log.info("Generating CSV with submission_id: " + submissionId);
Submission submission = maybeSubmission.get();
CsvDocument csvDoc = csvService.generateCsvFormattedData(List.of(submission), csvType);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=%s".formatted(csvService.generateCsvName(submission.getFlow(), csvType)));
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.headers(headers)
.body(csvDoc.getCsvData());
} else {
log.error("Attempted to download PDF with submission_id: " + submissionId + " but session_id was: "
+ httpSession.getAttribute("id"));
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(messageSource.getMessage("error.forbidden", null, locale));
}
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/ladocuploader/app/LaDocUploader.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication(scanBasePackages = {"org.ladocuploader.app", "formflow.library"})
@EntityScan(basePackages = {"org.ladocuploader.app", "formflow.library"})
@EnableConfigurationProperties
public class LaDocUploader {

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/ladocuploader/app/cli/FtpsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.ladocuploader.app.cli;

import java.io.IOException;

public interface FtpsClient {

void uploadFile(String zipFilename, byte[] data) throws IOException;
}
62 changes: 62 additions & 0 deletions src/main/java/org/ladocuploader/app/cli/FtpsClientImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.ladocuploader.app.cli;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

@Slf4j
@Component
@Profile("production")
public class FtpsClientImpl implements FtpsClient {

private final String username;
private final String password;
private final String uploadUrl;
private final String uploadDir;

public FtpsClientImpl(@Value("${ftps.username:}") String username, @Value("${ftps.password:}") String password, @Value("${ftps.upload-url:}") String uploadUrl, @Value("${ftps.upload-dir:}") String uploadDir) {
this.username = username;
this.password = password;
this.uploadUrl = uploadUrl;
this.uploadDir = uploadDir;
}

@Override
public void uploadFile(String zipFilename, byte[] data) throws IOException {
FTPSClient ftp = new FTPSClient();

ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));

ftp.connect(uploadUrl);
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
throw new IOException("Exception in connecting to FTP Server");
}

ftp.login(username, password);

ftp.changeWorkingDirectory(uploadDir);

ftp.execPBSZ(0);
ftp.execPROT("P");
ftp.enterLocalPassiveMode();
ftp.pasv();
InputStream local = new ByteArrayInputStream(data);
ftp.storeFile(zipFilename, local);
local.close();
ftp.completePendingCommand();

ftp.logout();
ftp.disconnect();
}
}
17 changes: 17 additions & 0 deletions src/main/java/org/ladocuploader/app/cli/MockFtpsClientImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.ladocuploader.app.cli;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Profile("!production")
public class MockFtpsClientImpl implements FtpsClient {

@Override
public void uploadFile(String zipFilename, byte[] data) {
// Do nothing
log.info("Mock uploading file " + zipFilename);
}
}
Loading

0 comments on commit 8fcfdf9

Please sign in to comment.