Skip to content

Commit b3c3cb5

Browse files
committed
feat: use dynamodb instead of pg
1 parent 3440b16 commit b3c3cb5

10 files changed

Lines changed: 104 additions & 122 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ dist
1414
/RUNNING_PID
1515
/.settings
1616
.DS_Store
17-
.env
17+
.env
18+
.vscode/

app/controllers/Application.scala

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Application @Inject()
9797
Ok(claSignView(authUrl, maybeGitHubAuthInfo, latestClaVersion, viewHelper.claText, maybePrUrl, svgInline))
9898
} recover {
9999
case AlreadyExistsException(claSignature) =>
100-
BadRequest(claAlreadySignedView(claSignature.signedOn))
100+
BadRequest(claAlreadySignedView(java.time.LocalDateTime.ofInstant(java.time.Instant.parse(claSignature.signedOn), java.time.ZoneOffset.UTC)))
101101
}
102102
}
103103
}
@@ -121,11 +121,11 @@ class Application @Inject()
121121

122122
maybeContact <- db.findContactByGitHubId(user.username)
123123
contact <- maybeContact.fold {
124-
db.createContact(Contact(-1, maybeFirstName, lastName, email, user.username))
124+
db.createContact(Contact("-1", maybeFirstName, lastName, email, user.username))
125125
} (Future.successful)
126126
existingClaSignatures <- db.findClaSignaturesByGitHubIds(Set(user))
127127
claSignature <- existingClaSignatures.headOption.fold {
128-
Future.successful(ClaSignature(-1, contact.gitHubId, LocalDateTime.now(), claVersion))
128+
Future.successful(ClaSignature("-1", contact.gitHubId, java.time.Instant.now().toString, claVersion))
129129
} { existingClaSignature =>
130130
Future.failed(AlreadyExistsException(existingClaSignature))
131131
}
@@ -159,7 +159,7 @@ class Application @Inject()
159159
} yield Redirect(routes.Application.signedCla(maybePrUrl))
160160
} recover {
161161
case AlreadyExistsException(claSignature) =>
162-
BadRequest(claAlreadySignedView(claSignature.signedOn))
162+
BadRequest(claAlreadySignedView(java.time.LocalDateTime.ofInstant(java.time.Instant.parse(claSignature.signedOn), java.time.ZoneOffset.UTC)))
163163
case e: Throwable =>
164164
Logger.error("CLA could not be signed.", e)
165165
val baseErrorMessage = "Could not sign the CLA"
@@ -319,7 +319,11 @@ class Application @Inject()
319319
val externalContributorsWithClas = external.map { contributorWithMetrics =>
320320
contributorWithMetrics.contributor match {
321321
case gitHubUser: GitHub.User =>
322-
contributorWithMetrics -> clasForExternalContributors.find(_.contactGitHubId == gitHubUser.username)
322+
val maybeCla = clasForExternalContributors.find(_.contactGitHubId == gitHubUser.username)
323+
val maybeClaWithDate = maybeCla.map { claSignature =>
324+
claSignature.copy(signedOn = java.time.LocalDateTime.ofInstant(java.time.Instant.parse(claSignature.signedOn), java.time.ZoneOffset.UTC).toString)
325+
}
326+
contributorWithMetrics -> maybeClaWithDate
323327
case _ =>
324328
contributorWithMetrics -> None
325329
}

app/models/ClaSignature.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,5 @@
77

88
package models
99

10-
import java.time.LocalDateTime
11-
12-
case class ClaSignature(id: Int, contactGitHubId: String, signedOn: LocalDateTime, claVersion: String)
10+
case class ClaSignature(id: String, contactGitHubId: String, signedOn: String, claVersion: String)
1311

app/models/Contact.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package models
99

1010

1111
// note: gitHubId is nullable in the DB but we only ever query for contacts with githubids, so we make it non-nullable here
12-
case class Contact(id: Int, firstName: Option[String], lastName: String, email: String, gitHubId: String)
12+
case class Contact(id: String, firstName: Option[String], lastName: String, email: String, gitHubId: String)
1313

1414
object Contact {
1515

app/modules/DatabaseModule.scala

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
package modules
99

10-
import com.github.mauricio.async.db.SSLConfiguration
11-
import com.github.mauricio.async.db.pool.{PartitionedConnectionPool, PoolConfiguration}
12-
import com.github.mauricio.async.db.postgresql.pool.PostgreSQLConnectionFactory
13-
import com.github.mauricio.async.db.postgresql.util.URLParser
14-
import io.getquill.{PostgresAsyncContext, SnakeCase}
10+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
11+
import software.amazon.awssdk.regions.Region
12+
import javax.inject.{Inject, Singleton}
1513
import javax.inject.{Inject, Singleton}
1614
import org.slf4j.LoggerFactory
1715
import play.api.inject.{ApplicationLifecycle, Binding, Module}
@@ -28,47 +26,23 @@ class DatabaseModule extends Module {
2826
}
2927

3028
trait Database {
31-
val ctx: PostgresAsyncContext[SnakeCase]
29+
val dynamoDb: DynamoDbClient
3230
}
3331

34-
@Singleton
35-
class DatabaseImpl @Inject()(lifecycle: ApplicationLifecycle, playConfig: Configuration) (implicit ec: ExecutionContext) extends Database {
3632

33+
@Singleton
34+
class DatabaseImpl @Inject()(lifecycle: ApplicationLifecycle, playConfig: Configuration) extends Database {
3735
private val log = LoggerFactory.getLogger(this.getClass)
36+
private val region = playConfig.getOptional[String]("aws.dynamodb.region").getOrElse("us-east-1")
37+
//private val endpoint = playConfig.getOptional[String]("aws.dynamodb.endpoint")
3838

39-
private val maybeDbUrl = playConfig.getOptional[String]("db.default.url")
40-
41-
private val config = maybeDbUrl.map(URLParser.parse(_)).getOrElse(URLParser.DEFAULT)
42-
43-
private val configWithMaybeSsl = playConfig.getOptional[String]("db.default.sslmode").fold(config) { sslmode =>
44-
val sslConfig = SSLConfiguration(Map("sslmode" -> sslmode))
45-
config.copy(ssl = sslConfig)
39+
val dynamoDb: DynamoDbClient = {
40+
val builder = DynamoDbClient.builder().region(Region.of(region))
41+
//endpoint.foreach(e => builder.endpointOverride(java.net.URI.create(e)))
42+
builder.build()
4643
}
47-
48-
private val connectionFactory = new PostgreSQLConnectionFactory(configWithMaybeSsl)
49-
50-
private val defaultPoolConfig = PoolConfiguration.Default
51-
52-
private val maxObjects = playConfig.getOptional[Int]("db.default.max-objects").getOrElse(defaultPoolConfig.maxObjects)
53-
private val maxIdleMillis = playConfig.getOptional[Long]("db.default.max-idle-millis").getOrElse(defaultPoolConfig.maxIdle)
54-
private val maxQueueSize = playConfig.getOptional[Int]("db.default.max-queue-size").getOrElse(defaultPoolConfig.maxQueueSize)
55-
private val validationInterval = playConfig.getOptional[Long]("db.default.max-queue-size").getOrElse(defaultPoolConfig.validationInterval)
56-
57-
private val poolConfig = new PoolConfiguration(maxObjects, maxIdleMillis, maxQueueSize, validationInterval)
58-
59-
private val numberOfPartitions = playConfig.getOptional[Int]("db.default.number-of-partitions").getOrElse(4)
60-
61-
private val pool = new PartitionedConnectionPool(
62-
connectionFactory,
63-
poolConfig,
64-
numberOfPartitions,
65-
ec
66-
)
67-
68-
lifecycle.addStopHook { () =>
69-
pool.close
70-
}
71-
72-
val ctx = new PostgresAsyncContext(SnakeCase, pool)
73-
44+
lifecycle.addStopHook(() => {
45+
dynamoDb.close()
46+
scala.concurrent.Future.successful(())
47+
})
7448
}

app/utils/ApplyEvolutions.scala

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,4 @@
77

88
package utils
99

10-
import org.flywaydb.play.PlayInitializer
11-
import play.api.inject.guice.GuiceApplicationBuilder
12-
13-
object ApplyEvolutions extends App {
14-
val app = new GuiceApplicationBuilder().build()
15-
16-
val playInitializer = app.injector.instanceOf[PlayInitializer]
17-
playInitializer.onStart()
18-
19-
app.stop()
20-
}
10+
// No-op: Database evolutions are not needed for DynamoDB

app/utils/DB.scala

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,77 @@ package utils
1010
import javax.inject.Inject
1111
import models.{ClaSignature, Contact}
1212
import modules.Database
13-
13+
import software.amazon.awssdk.services.dynamodb.model._
14+
import scala.collection.JavaConverters._
1415
import scala.concurrent.{ExecutionContext, Future}
15-
16-
class DB @Inject()(database: Database)(implicit ec: ExecutionContext) {
17-
18-
import database.ctx._
19-
20-
private val contacts = quote {
21-
querySchema[Contact](
22-
"salesforce.contact",
23-
_.gitHubId -> "sf_cla__github_id__c",
24-
_.firstName -> "firstname",
25-
_.lastName -> "lastname"
26-
)
27-
}
28-
29-
private val claSignatures = quote {
30-
querySchema[ClaSignature](
31-
"salesforce.sf_cla__cla_signature__c",
32-
_.signedOn -> "sf_cla__signed_on__c",
33-
_.claVersion -> "sf_cla__cla_version__c",
34-
_.contactGitHubId -> "sf_cla__contact__r__sf_cla__github_id__c"
35-
)
16+
import play.api.Configuration
17+
18+
19+
class DB @Inject()(database: Database, config: Configuration)(implicit ec: ExecutionContext) {
20+
private val dynamoDb = database.dynamoDb
21+
private val tableName = config.get[String]("aws.dynamodb.table")
22+
23+
def findContactByGitHubId(gitHubId: String): Future[Option[Contact]] = Future {
24+
val request = GetItemRequest.builder()
25+
.tableName(tableName)
26+
.key(Map("PK" -> AttributeValue.builder().s(s"CONTACT#$gitHubId").build()).asJava)
27+
.build()
28+
val result = dynamoDb.getItem(request)
29+
if (result.hasItem) {
30+
val item = result.item().asScala
31+
Some(Contact(
32+
id = item.getOrElse("id", AttributeValue.builder().s("").build()).s(),
33+
firstName = Some(item.getOrElse("firstName", AttributeValue.builder().s("").build()).s()),
34+
lastName = item.getOrElse("lastName", AttributeValue.builder().s("").build()).s(),
35+
email = item.getOrElse("email", AttributeValue.builder().s("").build()).s(),
36+
gitHubId = gitHubId
37+
))
38+
} else None
3639
}
3740

38-
def findContactByGitHubId(gitHubId: String): Future[Option[Contact]] = {
39-
val queryResult = run {
40-
contacts.filter(_.gitHubId == lift(gitHubId))
41-
}
42-
43-
queryResult.map(_.headOption)
44-
}
45-
46-
def createContact(contact: Contact): Future[Contact] = {
47-
val queryResult = run {
48-
contacts.insert(lift(contact)).returning(_.id)
49-
}
50-
51-
queryResult.map(newId => contact.copy(id = newId))
41+
def createContact(contact: Contact): Future[Contact] = Future {
42+
val item = Map(
43+
"PK" -> AttributeValue.builder().s(s"CONTACT#${contact.gitHubId}").build(),
44+
"id" -> AttributeValue.builder().s(contact.id).build(),
45+
"firstName" -> AttributeValue.builder().s(contact.firstName.getOrElse("")).build(),
46+
"lastName" -> AttributeValue.builder().s(contact.lastName).build(),
47+
"email" -> AttributeValue.builder().s(contact.email).build()
48+
).asJava
49+
val request = PutItemRequest.builder().tableName(tableName).item(item).build()
50+
dynamoDb.putItem(request)
51+
contact
5252
}
5353

54-
def createClaSignature(claSignature: ClaSignature): Future[ClaSignature] = {
55-
val queryResult = run {
56-
claSignatures.insert(lift(claSignature)).returning(_.id)
57-
}
58-
59-
queryResult.map(newId => claSignature.copy(id = newId))
54+
def createClaSignature(claSignature: ClaSignature): Future[ClaSignature] = Future {
55+
val item = Map(
56+
"PK" -> AttributeValue.builder().s(s"CLASIGNATURE#${claSignature.contactGitHubId}").build(),
57+
"id" -> AttributeValue.builder().s(claSignature.id).build(),
58+
"signedOn" -> AttributeValue.builder().s(claSignature.signedOn).build(),
59+
"claVersion" -> AttributeValue.builder().s(claSignature.claVersion).build(),
60+
"contactGitHubId" -> AttributeValue.builder().s(claSignature.contactGitHubId).build()
61+
).asJava
62+
val request = PutItemRequest.builder().tableName(tableName).item(item).build()
63+
dynamoDb.putItem(request)
64+
claSignature
6065
}
6166

62-
def findClaSignaturesByGitHubIds(gitHubIds: Set[GitHub.User]): Future[Set[ClaSignature]] = {
63-
val queryResult = run {
64-
claSignatures.filter(claSignature => liftQuery(gitHubIds.map(_.username)).contains(claSignature.contactGitHubId))
65-
}
66-
67-
queryResult.map(_.toSet)
67+
def findClaSignaturesByGitHubIds(gitHubIds: Set[GitHub.User]): Future[Set[ClaSignature]] = Future {
68+
gitHubIds.map { user =>
69+
val request = GetItemRequest.builder()
70+
.tableName(tableName)
71+
.key(Map("PK" -> AttributeValue.builder().s(s"CLASIGNATURE#${user.username}").build()).asJava)
72+
.build()
73+
val result = dynamoDb.getItem(request)
74+
if (result.hasItem) {
75+
val item = result.item().asScala
76+
Some(ClaSignature(
77+
id = item.getOrElse("id", AttributeValue.builder().s("").build()).s(),
78+
signedOn = item.getOrElse("signedOn", AttributeValue.builder().s("").build()).s(),
79+
claVersion = item.getOrElse("claVersion", AttributeValue.builder().s("").build()).s(),
80+
contactGitHubId = item.getOrElse("contactGitHubId", AttributeValue.builder().s("").build()).s()
81+
))
82+
} else None
83+
}.flatten.toSet
6884
}
6985

7086
}

app/views/auditRepo.scala.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
@(externalContributors: Set[(GitHub.ContributorWithMetrics, Option[ClaSignature])], internalContributors: Set[GitHub.ContributorWithMetrics])
1414

15-
@dateFormat(dateTime: LocalDateTime) = @{
16-
dateTime.format(DateTimeFormatter.ofPattern("MMM dd, yyyy"))
15+
@dateFormat(dateTime: String) = @{
16+
LocalDateTime.parse(dateTime).format(DateTimeFormatter.ofPattern("MMM dd, yyyy"))
1717
}
1818

1919
@if(internalContributors.nonEmpty) {

build.sbt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ libraryDependencies ++= Seq(
2424

2525
"com.pauldijou" %% "jwt-play-json" % "0.19.0",
2626

27-
"org.postgresql" % "postgresql" % "42.1.4",
28-
"org.flywaydb" %% "flyway-play" % "4.0.0",
29-
30-
"io.getquill" %% "quill-async-postgres" % "2.5.4",
27+
"software.amazon.awssdk" % "dynamodb" % "2.20.145",
3128

3229
"org.webjars" %% "webjars-play" % "2.6.3",
3330
"org.webjars" % "salesforce-lightning-design-system" % "2.4.1",

conf/application.conf

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
play.modules.enabled += "org.flywaydb.play.PlayModule"
1+
#play.modules.enabled += "org.flywaydb.play.PlayModule"
22
play.modules.enabled += "modules.DatabaseModule"
33

44
play.http.filters = "utils.Filters"
@@ -12,10 +12,12 @@ play.filters.headers.contentSecurityPolicy = "default-src 'self' https://cdn.jsd
1212

1313
play.i18n.langs = ["en"]
1414

15-
db.default.driver = "org.postgresql.Driver"
16-
db.default.url = "postgres://salesforcecla:password@localhost:5432/salesforcecla"
17-
db.default.url = ${?DATABASE_URL}
18-
db.default.sslmode = ${?PGSSLMODE}
15+
# db.default.driver = "org.postgresql.Driver"
16+
# db.default.url = "postgres://salesforcecla:password@localhost:5432/salesforcecla"
17+
# db.default.url = ${?DATABASE_URL}
18+
# db.default.sslmode = ${?PGSSLMODE}
19+
aws.dynamodb.table = "dr-cla"
20+
aws.dynamodb.table = ${?DYNAMODB_TABLE}
1921

2022
webjars.use-cdn=${?WEBJARS_USE_CDN}
2123

0 commit comments

Comments
 (0)