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

Adds CSV generation and transmission #427

Merged
merged 60 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c821f51
use standalone csv generation w/o pdf classes
lkemperman-cfa Nov 6, 2023
990b295
mark parent guardian class as component
lkemperman-cfa Nov 6, 2023
f21d470
progress on csv approach
lkemperman-cfa Nov 9, 2023
78ee677
first working implementation for parent guardian csv
lkemperman-cfa Nov 9, 2023
297bce8
remove unused pdf code
lkemperman-cfa Nov 13, 2023
76377c5
add all templates and clean up code
lkemperman-cfa Nov 14, 2023
2db1814
refactoring + add computed field (not yet working)
lkemperman-cfa Nov 15, 2023
fecb4e7
add few comments
lkemperman-cfa Nov 15, 2023
345f380
example of converted field working
lkemperman-cfa Nov 16, 2023
2b0e707
progress on models
lkemperman-cfa Nov 27, 2023
7a8655a
refactoring, improvements and broken shared model
lkemperman-cfa Nov 28, 2023
c070e47
Updates the code to be more generic.
bseeger Nov 29, 2023
73065f4
Attempts to add converter
bseeger Nov 30, 2023
35bcc63
First pass with functioning converter and header column name mapping.
bseeger Dec 4, 2023
354490c
Some cleanup
bseeger Dec 4, 2023
b2be641
Updating to merge with Lauren's stuff.
bseeger Dec 5, 2023
e09da79
add generate migration script and transmission log table migration
lkemperman-cfa Nov 28, 2023
4130a1e
make transmission model class
lkemperman-cfa Nov 28, 2023
51708e3
initial modifications to support csv transmission
lkemperman-cfa Nov 30, 2023
acab4cd
change flow name to laDigitalAssister
lkemperman-cfa Nov 30, 2023
7fa0b2c
add after save action to handle application signed
lkemperman-cfa Nov 30, 2023
d298d2d
resolved issues - code is compiling
lkemperman-cfa Nov 30, 2023
8aee18b
more TODOs on transmitter commands
lkemperman-cfa Nov 30, 2023
6eabebe
schema changes + still wip cron job transmission command
lkemperman-cfa Dec 4, 2023
b50fa93
in progress
lkemperman-cfa Dec 5, 2023
89f3e92
add submission error types enum
lkemperman-cfa Dec 5, 2023
9f6327d
Moves the enums into their own classes. Revamps code to handle that.
bseeger Dec 5, 2023
5cfe060
first version of the zipFiles without error messages
lkemperman-cfa Dec 5, 2023
54a1889
fixed the model
lkemperman-cfa Dec 5, 2023
53089d2
use the new enums
lkemperman-cfa Dec 5, 2023
a9e346c
fix transmission repository and database schema
lkemperman-cfa Dec 6, 2023
f2c2d37
ParentGuardian, Student and Relationship are at a good point.
bseeger Dec 6, 2023
25c2d96
sftp changes
lkemperman-cfa Dec 6, 2023
f9511a1
modifications to enum, service and commands
lkemperman-cfa Dec 6, 2023
40a8545
creates basic code for ECE and WIC apps
bseeger Dec 6, 2023
e33b2b6
add zip entries
lkemperman-cfa Dec 7, 2023
fd15072
delete temporary files
lkemperman-cfa Dec 7, 2023
d60de9b
fix transmitter commands
lkemperman-cfa Dec 7, 2023
005d12a
add some todos + fix transmitter query
lkemperman-cfa Dec 8, 2023
fe952e9
Start of adding ECE application. total WIP
bseeger Dec 7, 2023
54dc03e
Updates the datatype returned from getting a CsvPackages error messages.
bseeger Dec 8, 2023
fd5cd54
changes to support the upload location
lkemperman-cfa Dec 8, 2023
b97a7e1
error propagation working
lkemperman-cfa Dec 8, 2023
a713e9a
add todo around submitting / failed cases
lkemperman-cfa Dec 8, 2023
a91e588
add environment path for sftp in application.yml
lkemperman-cfa Dec 8, 2023
e503bf8
Finished adding variables for ECE application. Still a WIP.
bseeger Dec 8, 2023
60f946a
remove baseline on migration from application.yaml
lkemperman-cfa Dec 11, 2023
e5cfc02
add .log to gitignore so spring-shell.log doesn't get added
lkemperman-cfa Dec 11, 2023
76dc3a3
add column definition for submission errors
lkemperman-cfa Dec 11, 2023
d8969ec
Start adding logic for mapping ece fields to ours in for Jackson
bseeger Dec 11, 2023
289a469
Adjusts to data model name changes.
bseeger Dec 11, 2023
7821da5
use standalone csv generation w/o pdf classes
lkemperman-cfa Nov 6, 2023
33d2f46
progress on csv approach
lkemperman-cfa Nov 9, 2023
754c93a
initial modifications to support csv transmission
lkemperman-cfa Nov 30, 2023
8766385
ignore partial test
lkemperman-cfa Dec 11, 2023
c310e84
fix transmitter test class
lkemperman-cfa Dec 11, 2023
b2c44ef
Merge branch 'main' into wic-ece-csv-transmission
lkemperman-cfa Dec 13, 2023
e3898d1
Addresses some PR code cleanup
bseeger Dec 13, 2023
0c9c25b
Updates code to protect against bad data
bseeger Dec 13, 2023
96f98f2
Patches up a few more error spots
bseeger Dec 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
13 changes: 12 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ 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.0.4'
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.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.7.0'

if (profile == 'dev' || useLocalLibrary == 'true') {
implementation fileTree(dir: "$rootDir/../form-flow/build/libs", include: '*.jar')
Expand All @@ -53,9 +56,11 @@ dependencies {
testImplementation 'org.projectlombok:lombok:1.18.30'

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

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

testImplementation 'junit:junit:4.13.1'


compileOnly 'org.projectlombok:lombok'

developmentOnly 'org.springframework.boot:spring-boot-devtools'
Expand All @@ -81,6 +86,12 @@ springBoot {
buildInfo()
}

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

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"));
Fixed Show fixed Hide fixed
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
17 changes: 17 additions & 0 deletions src/main/java/org/ladocuploader/app/cli/MockSftpClientImpl.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 MockSftpClientImpl implements SftpClient {

@Override
public void uploadFile(String zipFilename, String filePath) {
// Do nothing
log.info("Mock uploading file " + zipFilename);
}
}
10 changes: 10 additions & 0 deletions src/main/java/org/ladocuploader/app/cli/SftpClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.ladocuploader.app.cli;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;

public interface SftpClient {


void uploadFile(String zipFilename, String filePath) throws JSchException, SftpException;

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

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

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

import java.util.List;

@Component
@Slf4j
@Profile("production")
public class SftpClientImpl implements SftpClient {

String username;
String password;
String uploadUrl;

String environmentPath;

public SftpClientImpl(@Value("${sftp.username:}") String username, @Value("${sftp.password:}") String password, @Value("${sftp.upload-url:}") String uploadUrl,
@Value("${sftp.environment-path:}") String environmentPath) {
this.username = username;
this.password = password;
this.uploadUrl = uploadUrl;
this.environmentPath = environmentPath;
}

@Override
public void uploadFile(String zipFilename, String filePath) throws JSchException, SftpException {
JSch jsch = new JSch();
jsch.setKnownHosts("src/main/resources/known_hosts");
Session jschSession = jsch.getSession(username, uploadUrl);
jschSession.setPassword(password);
jschSession.connect(10000);

Channel sftp = jschSession.openChannel("sftp");
sftp.connect(5000);

ChannelSftp channelSftp = (ChannelSftp) sftp;
String destinationFilePath = String.join("/", List.of(filePath + "-" + this.environmentPath, zipFilename));
log.info(destinationFilePath);

channelSftp.put(zipFilename, destinationFilePath );

channelSftp.exit();
}
}

Loading