Skip to content

Commit 3bb8e61

Browse files
author
Nick Grippin
committed
Merge branch 'master' into n52
2 parents 5f236df + c853f49 commit 3bb8e61

File tree

62 files changed

+757
-1783
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+757
-1783
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ docker-compose -f docker-dev.yml up
157157

158158
When finished, use `docker-compose down` to gracefully stop the running containers.
159159

160+
In order to not get Postgres connection error upon signing a submission, you may want to start up the keycloak image as well.
161+
```shell
162+
docker-compose up keycloak
163+
```
164+
Not having the keycloak image running will produce an error in the console, but will not cause the platform to crash.
160165

161166
#### Running the API
162167

@@ -214,13 +219,13 @@ The Filing API will run on `$(docker-machine ip):8080`
214219
The Public API will run on `$(docker-machine ip):8082`
215220

216221
By default, the `HDMA Platform` runs with a log level of `INFO`. This can be changed by establishing a different log level in the `HMDA_LOGLEVEL` environment variable.
217-
For the different logging options, see the [reference.conf](https://github.com/akka/akka/blob/master/akka-actor/src/main/resources/reference.conf#L38) default configuration file for `Akka`.
222+
For the different logging options, see the [reference.conf](https://github.com/akka/akka/blob/master/akka-actor/src/main/resources/reference.conf#L38) default configuration file for `Akka`.
218223

219224
#### To run the entire platform
220225

221226
1. Ensure you have a Docker Machine with sufficient resources, as described in the [Docker](#docker) section above.
222227

223-
1. Clone [hmda-platform-ui](https://github.com/cfpb/hmda-platform-ui) and
228+
1. Clone [hmda-platform-ui](https://github.com/cfpb/hmda-platform-ui) and
224229
[hmda-platform-auth](https://github.com/cfpb/hmda-platform-auth) into the same
225230
directory as hmda-platform.
226231

@@ -270,7 +275,7 @@ For the different logging options, see the [reference.conf](https://github.com/a
270275
**Note:** You must register with an email address from our whitelist of email domains.
271276
For convenience, `bank0.com` and `bank1.com` address should be available automatically.
272277

273-
1. Browse to the mock email server at https://192.168.99.100:8443/mail/, and select the
278+
1. Browse to the mock email server at https://192.168.99.100:8443/mail/, and select the
274279
verification link in the email found there. This should take you back to the HMDA
275280
filing web app, now logged in.
276281

@@ -281,7 +286,7 @@ For the different logging options, see the [reference.conf](https://github.com/a
281286

282287
##### Updating an existing system
283288

284-
If you've updated any of the hmda-platform services, and would like to see those
289+
If you've updated any of the hmda-platform services, and would like to see those
285290
changes reflected in the Docker Compose setup, the simplest way to do this is to
286291
rebuild everything from scratch. The following command should be executed from
287292
within the `hmda-platform` directory.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
hmda {
2+
connectionFlowParallelism = 5
3+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package hmda.api.util
2+
3+
import akka.NotUsed
4+
import akka.actor.ActorSystem
5+
import akka.http.scaladsl.Http
6+
import akka.http.scaladsl.model.{ HttpMethods, HttpRequest, HttpResponse, Uri }
7+
import akka.http.scaladsl.unmarshalling.Unmarshal
8+
import akka.stream.ActorMaterializer
9+
import akka.stream.scaladsl.{ Flow, Framing }
10+
import akka.util.ByteString
11+
import com.typesafe.config.ConfigFactory
12+
import org.slf4j.Logger
13+
import scala.concurrent.ExecutionContext
14+
15+
trait FlowUtils {
16+
17+
implicit val system: ActorSystem
18+
implicit val materializer: ActorMaterializer
19+
implicit val ec: ExecutionContext
20+
val config = ConfigFactory.load()
21+
val parallelism = config.getInt("hmda.connectionFlowParallelism")
22+
23+
def singleConnectionFlow: Flow[HttpRequest, HttpResponse, NotUsed] =
24+
Flow[HttpRequest]
25+
.mapAsync[HttpResponse](parallelism)(request => {
26+
for {
27+
response <- Http().singleRequest(request)
28+
} yield response
29+
})
30+
31+
def framing: Flow[ByteString, ByteString, NotUsed] = {
32+
Framing.delimiter(ByteString("\n"), maximumFrameLength = 65536, allowTruncation = true)
33+
}
34+
35+
def byte2StringFlow: Flow[ByteString, String, NotUsed] =
36+
Flow[ByteString].map(bs => bs.utf8String)
37+
38+
def sendGetRequest(req: String, url: Uri) = {
39+
val request = HttpRequest(HttpMethods.GET, uri = s"$url/$req")
40+
for {
41+
response <- Http().singleRequest(request)
42+
content <- Unmarshal(response.entity).to[String]
43+
} yield content
44+
}
45+
46+
def exitSys(log: Logger, errorMessage: String, code: Int) = {
47+
log.error(errorMessage)
48+
system.terminate()
49+
Thread.sleep(100)
50+
sys.exit(code)
51+
}
52+
}

api/src/main/resources/application-dev.conf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ hmda {
2828
timeout = ${?HMDA_HTTP_TIMEOUT}
2929
}
3030
isDemo = true
31+
panel {
32+
tcp {
33+
host = "0.0.0.0"
34+
host = ${?HMDA_PANEL_LOADER_HOST}
35+
port = "8888"
36+
port = ${?HMDA_PANEL_LOADER_PORT}
37+
timeout = 5
38+
}
39+
}
3140
actor-flow-parallelism = 4
3241
}
3342

api/src/main/resources/application.conf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,26 @@ hmda {
2727
timeout = 10
2828
timeout = ${?HMDA_HTTP_TIMEOUT}
2929
}
30+
mail {
31+
host = "mail_dev"
32+
host = ${?HMDA_MAIL_HOST}
33+
port = "25"
34+
port = ${?HMDA_MAIL_PORT}
35+
senderAddress = "no-reply@cfpb.gov"
36+
senderAddress = ${?HMDA_MAIL_SENDER_ADDRESS}
37+
}
3038
isDemo = false
3139
isDemo = ${?HMDA_IS_DEMO}
40+
panel {
41+
tcp {
42+
host = "0.0.0.0"
43+
host = ${?HMDA_PANEL_LOADER_HOST}
44+
port = "8888"
45+
port = ${?HMDA_PANEL_LOADER_PORT}
46+
timeout = 5
47+
parallelism = 5
48+
}
49+
}
3250
}
3351

3452
api-dispatcher {

api/src/main/resources/logback.xml

Lines changed: 0 additions & 23 deletions
This file was deleted.

api/src/main/scala/hmda/api/http/admin/InstitutionAdminHttpApi.scala

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,9 @@ import hmda.persistence.HmdaSupervisor.FindFilings
1818
import hmda.persistence.institutions.FilingPersistence.CreateFiling
1919
import hmda.persistence.institutions.{ FilingPersistence, InstitutionPersistence }
2020
import hmda.persistence.institutions.InstitutionPersistence.{ CreateInstitution, ModifyInstitution }
21-
import hmda.persistence.messages.CommonMessages.Event
2221
import hmda.persistence.model.HmdaSupervisorActor.FindActorByName
23-
import hmda.query.projections.institutions.InstitutionDBProjection.{ CreateSchema, DeleteSchema }
24-
import hmda.query.view.institutions.InstitutionView
25-
import hmda.query.view.messages.CommonViewMessages.GetProjectionActorRef
2622

2723
import scala.concurrent.ExecutionContext
28-
import scala.util.matching.Regex
2924
import scala.util.{ Failure, Success }
3025

3126
trait InstitutionAdminHttpApi
@@ -85,31 +80,5 @@ trait InstitutionAdminHttpApi
8580
}
8681
}
8782

88-
private val cdRegex = new Regex("create|delete")
89-
90-
def institutionsSchemaPath(querySupervisor: ActorRef) =
91-
path("institutions" / cdRegex) { command =>
92-
extractExecutionContext { executor =>
93-
implicit val ec: ExecutionContext = executor
94-
val fInstitutionsActor = (querySupervisor ? FindActorByName(InstitutionView.name)).mapTo[ActorRef]
95-
val message = command match {
96-
case "create" => CreateSchema
97-
case "delete" => DeleteSchema
98-
}
99-
timedGet { uri =>
100-
val event = for {
101-
instAct <- fInstitutionsActor
102-
dbAct <- (instAct ? GetProjectionActorRef).mapTo[ActorRef]
103-
cd <- (dbAct ? message).mapTo[Event]
104-
} yield cd
105-
106-
onComplete(event) {
107-
case Success(response) => complete(ToResponseMarshallable(StatusCodes.Accepted -> response.toString))
108-
case Failure(error) => completeWithInternalError(uri, error)
109-
}
110-
}
111-
}
112-
}
113-
114-
def institutionAdminRoutes(supervisor: ActorRef, querySupervisor: ActorRef) = encodeResponse { institutionsWritePath(supervisor) ~ institutionsSchemaPath(querySupervisor) }
83+
def institutionAdminRoutes(supervisor: ActorRef, querySupervisor: ActorRef) = encodeResponse { institutionsWritePath(supervisor) }
11584
}

api/src/main/scala/hmda/api/http/institutions/UploadPaths.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import hmda.persistence.processing.ProcessingMessages.{ CompleteUpload, Persiste
2727
import hmda.persistence.processing.SubmissionManager
2828
import hmda.persistence.processing.SubmissionManager.AddFileName
2929
import hmda.query.HmdaQuerySupervisor.FindHmdaFilingView
30-
import hmda.query.projections.filing.HmdaFilingDBProjection.{ CreateSchema, DeleteLars }
3130

3231
import scala.util.{ Failure, Success }
3332

@@ -61,10 +60,6 @@ trait UploadPaths extends InstitutionProtocol with ApiErrorProtocol with Submiss
6160

6261
onComplete(fUploadSubmission) {
6362
case Success((submission, true, processingActor)) =>
64-
//TODO: remove this when removing PostgreSQL from project
65-
val queryProjector = system.actorSelection(s"/user/query-supervisor/HmdaFilingView-$period/queryProjector")
66-
queryProjector ! CreateSchema
67-
queryProjector ! DeleteLars(institutionId)
6863
uploadFile(processingActor, uploadTimestamp, uri.path, submission)
6964
case Success((_, false, _)) =>
7065
val errorResponse = Failed(s"Submission $seqNr not available for upload")

api/src/main/scala/hmda/api/http/institutions/submissions/SubmissionIrsPaths.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import hmda.api.model.{ Irs, IrsResponse }
1212
import hmda.api.protocol.processing.MsaProtocol
1313
import hmda.census.model.Msa
1414
import hmda.model.fi.SubmissionId
15-
import hmda.query.repository.filing.FilingComponent
1615
import hmda.validation.ValidationStats.FindIrsStats
1716

1817
import scala.concurrent.{ ExecutionContext, Future }
@@ -21,8 +20,7 @@ import scala.util.{ Failure, Success }
2120
trait SubmissionIrsPaths
2221
extends HmdaCustomDirectives
2322
with RequestVerificationUtils
24-
with MsaProtocol
25-
with FilingComponent {
23+
with MsaProtocol {
2624

2725
implicit val system: ActorSystem
2826
implicit val materializer: ActorMaterializer

api/src/main/scala/hmda/api/http/institutions/submissions/SubmissionSignPaths.scala

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package hmda.api.http.institutions.submissions
22

3+
import java.time.{ ZoneOffset, ZonedDateTime }
4+
35
import akka.actor.{ ActorRef, ActorSystem }
46
import akka.event.LoggingAdapter
57
import akka.pattern.ask
@@ -22,6 +24,15 @@ import spray.json.{ JsBoolean, JsFalse, JsObject, JsTrue }
2224

2325
import scala.util.{ Failure, Success }
2426
import scala.concurrent.{ ExecutionContext, Future }
27+
import javax.mail._
28+
import javax.mail.internet.{ InternetAddress, MimeMessage }
29+
30+
import com.typesafe.config.ConfigFactory
31+
import hmda.model.institution.Institution
32+
import hmda.persistence.model.HmdaSupervisorActor.FindActorByName
33+
import hmda.query.repository.KeyCloakRepository
34+
import hmda.query.view.institutions.InstitutionView
35+
import hmda.query.view.institutions.InstitutionView.GetInstitutionById
2536

2637
trait SubmissionSignPaths
2738
extends InstitutionProtocol
@@ -30,7 +41,8 @@ trait SubmissionSignPaths
3041
with EditResultsProtocol
3142
with HmdaCustomDirectives
3243
with RequestVerificationUtils
33-
with ValidationErrorConverter {
44+
with ValidationErrorConverter
45+
with KeyCloakRepository {
3446

3547
implicit val system: ActorSystem
3648
implicit val materializer: ActorMaterializer
@@ -44,7 +56,7 @@ trait SubmissionSignPaths
4456
val submissionId = SubmissionId(institutionId, period, id)
4557
timedGet { uri =>
4658
completeVerified(supervisor, querySupervisor, institutionId, period, id, uri) {
47-
completeWithSubmissionReceipt(supervisor, submissionId, uri)
59+
completeWithSubmissionReceipt(supervisor, submissionId, uri, signed = false)
4860
}
4961
} ~
5062
timedPost { uri =>
@@ -59,7 +71,7 @@ trait SubmissionSignPaths
5971
s <- actor ? hmda.persistence.processing.ProcessingMessages.Signed
6072
} yield s
6173
onComplete(fSign) {
62-
case Success(Some(_)) => completeWithSubmissionReceipt(supervisor, submissionId, uri)
74+
case Success(Some(_)) => completeWithSubmissionReceipt(supervisor, submissionId, uri, signed = true)
6375
case Success(_) =>
6476
val errorResponse = ErrorResponse(400, "Illegal State: Submission must be Validated or ValidatedWithErrors to sign", uri.path)
6577
complete(ToResponseMarshallable(StatusCodes.BadRequest -> errorResponse))
@@ -76,7 +88,7 @@ trait SubmissionSignPaths
7688
}
7789
}
7890

79-
private def completeWithSubmissionReceipt(supervisor: ActorRef, subId: SubmissionId, uri: Uri)(implicit ec: ExecutionContext) = {
91+
private def completeWithSubmissionReceipt(supervisor: ActorRef, subId: SubmissionId, uri: Uri, signed: Boolean)(implicit ec: ExecutionContext) = {
8092
val fSubmissionsActor = (supervisor ? FindSubmissions(SubmissionPersistence.name, subId.institutionId, subId.period)).mapTo[ActorRef]
8193
val fSubmission = for {
8294
a <- fSubmissionsActor
@@ -85,8 +97,69 @@ trait SubmissionSignPaths
8597

8698
onComplete(fSubmission) {
8799
case Success(sub) =>
100+
if (signed) {
101+
emailSignature(supervisor, sub)
102+
}
88103
complete(ToResponseMarshallable(Receipt(sub.end, sub.receipt, sub.status)))
89104
case Failure(error) => completeWithInternalError(uri, error)
90105
}
91106
}
107+
108+
private def emailSignature(supervisor: ActorRef, submission: Submission)(implicit ec: ExecutionContext) = {
109+
val emails = findEmailsById(submission.id.institutionId)
110+
val querySupervisor = system.actorSelection("/user/query-supervisor/singleton")
111+
val fInstitutionsActor = (querySupervisor ? FindActorByName(InstitutionView.name)).mapTo[ActorRef]
112+
val fName = for {
113+
a <- fInstitutionsActor
114+
i <- (a ? GetInstitutionById(submission.id.institutionId)).mapTo[Institution]
115+
e <- emails
116+
} yield (i.respondent.name, e)
117+
118+
fName.onComplete({
119+
case Success((instName, emailSeq)) =>
120+
emailSeq.foreach(t => {
121+
val username = t._1 + " " + t._2
122+
sendMail(t._3, username, submission, instName)
123+
})
124+
case Failure(error) => log.error(error, s"An error has occured retrieving the institution name for ID ${submission.id.institutionId}")
125+
})
126+
}
127+
128+
private def sendMail(address: String, username: String, submission: Submission, instName: String) = {
129+
val config = ConfigFactory.load()
130+
val host = config.getString("hmda.mail.host")
131+
val port = config.getString("hmda.mail.port")
132+
val senderAddress = config.getString("hmda.mail.senderAddress")
133+
134+
val properties = System.getProperties
135+
properties.put("mail.smtp.host", host)
136+
properties.put("mail.smtp.port", port)
137+
138+
val session = Session.getDefaultInstance(properties)
139+
val message = new MimeMessage(session)
140+
141+
val date = getFormattedDate
142+
143+
val text = s"$username,\n\nCongratulations, you've completed filing your HMDA data for $instName for filing period ${submission.id.period}.\n" +
144+
s"We received your filing on: $date\n" +
145+
s"Your receipt is: ${submission.receipt}"
146+
message.setFrom(new InternetAddress(senderAddress))
147+
message.setRecipients(Message.RecipientType.TO, address)
148+
message.setSubject("HMDA Filing Successful")
149+
message.setText(text)
150+
151+
log.info(s"Sending message to $address with the message \n$text")
152+
Transport.send(message)
153+
}
154+
155+
private def getFormattedDate: String = {
156+
val offset = ZoneOffset.ofHours(-5)
157+
val zonedTime = ZonedDateTime.now(offset)
158+
159+
val day = zonedTime.getDayOfMonth
160+
val month = zonedTime.getMonthValue
161+
val year = zonedTime.getYear
162+
163+
s"$month/$day/$year"
164+
}
92165
}

0 commit comments

Comments
 (0)