-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRequestUnicorn.scala
164 lines (144 loc) · 4.87 KB
/
RequestUnicorn.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//> using target.platform "scala-native"
//> using nativeLinking "-lssl", "-lcrypto"
//> using nativeLinking "--target=x86_64-unknown-linux-gnu", "-static-libstdc++", "-L/usr/lib64/"
//> using dep "com.armanbilge::epollcat::0.1.4"
//> using dep "org.typelevel::cats-effect::3.5.0"
// Requires: smithy4s generate --dependencies com.disneystreaming.smithy:aws-dynamodb-spec:2023.02.10 -o ./handlers/wildrides
package wildrides
import runtime.*
import smithy4s.aws.*
import smithy4s.aws.http4s.AwsHttp4sBackend
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.dynamodb.*
import upickle.default.*
import upickle.implicits.key
import java.time.LocalDateTime
import scala.jdk.CollectionConverters.*
import scala.util.Try
import cats.effect.*
import cats.effect.unsafe.IORuntime
import epollcat.unsafe.EpollRuntime
import org.http4s.ember.client.EmberClientBuilder
import scribe.*
given IORuntime = EpollRuntime.global
case class Unicorn(
name: String,
color: String,
gender: String
) derives Writer
case class Input(pickupLocation: PickupLocation) derives Reader
case class PickupLocation(
latitude: Double,
longitude: Double
) derives Reader
enum Response derives Writer:
case Failure(
error: String,
reference: String
)
case Success(
rideId: String,
unicorn: Unicorn,
unicornName: String,
eta: String,
rider: String
)
lazy val asEvent =
APIGatewayProxyResponseEvent(
statusCode = this match
case _: Success => 201
case _: Failure => 500
,
headers = Map(
"Content-Type" -> "application/json",
"Access-Control-Allow-Origin" -> "*"
),
body = write(this)
)
case class RequestContext(authorizer: Authorizer) derives Reader
case class Authorizer(claims: Map[String, String]) derives Reader
case class APIGatewayProxyRequestEvent(
requestContext: RequestContext,
body: String
) derives Reader {
val input: Input = read(body)
}
case class APIGatewayProxyResponseEvent(
headers: Map[String, String],
statusCode: Int,
body: String
) derives Writer
@main def RequestUnicorn =
new LambdaHandler[APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent]:
override def run(
event: APIGatewayProxyRequestEvent
)(using Context): APIGatewayProxyResponseEvent = {
val authorizer = event.requestContext.authorizer
if authorizer == null
then
return Response
.Failure("Authorization not configured", context.getAwsRequestId())
.asEvent
val rideId =
scala.util.Random.alphanumeric
.take(16)
.mkString // UUID.randomUUID().toString()
val username = authorizer.claims("cognito:username")
scribe.info(s"Received event: rideId=$rideId, event=$event")
val Input(pickupLocation) = event.input
(for
unicorn <- findUnicorn(event.input.pickupLocation)
_ <- recordRide(rideId, username, unicorn)
yield Response.Success(
rideId = rideId,
unicorn = unicorn,
unicornName = unicorn.name,
eta = "30 seconds",
rider = username
))
.recover { err =>
scribe.error(err)
Response.Failure(err.getMessage(), context.getAwsRequestId())
}
.get
.asEvent
}
lazy val fleet = Seq(
Unicorn("Bucephalus", "Golden", "Male"),
Unicorn("Shadowfax", "White", "Male"),
Unicorn("Rocinante", "Yellow", "Female")
)
def findUnicorn(pickupLocation: PickupLocation): Try[Unicorn] = Try:
scribe.info(s"Finding unicorn for ${pickupLocation.latitude},${pickupLocation.longitude}")
scala.util.Random.shuffle(fleet).head
import smithy4s.aws.http4s.ServiceOps
val dynamoDbClient = for {
logger <- Resource.make(config.loggerFactory.fromName("client-dynamoDb"))(_ => IO.unit)
httpClient <- EmberClientBuilder.default[IO].withLogger(logger).build
dynamodb <- DynamoDB.simpleAwsClient(httpClient, AwsRegion.EU_CENTRAL_1)
} yield dynamodb
def recordRide(rideId: String, username: String, unicorn: Unicorn): Try[Unit] = Try:
dynamoDbClient
.use { dynamoDb =>
dynamoDb
.putItem(
tableName = TableName("Rides"),
item = Map(
"RideId" -> rideId,
"User" -> username,
"Unicorn" -> write(unicorn),
"UnicornName" -> unicorn.name,
"RequestTime" -> LocalDateTime.now().toString()
).map: (k, v) =>
AttributeName(k) -> AttributeValue.SCase(StringAttributeValue(v))
).flatMap(res => IO.println(s"Recorded ride with id=$rideId, res=$res"))
}
.runSync()
implicit class NativeIOOps[T](val io: IO[T]) extends AnyVal {
def runSync()(implicit runtime: IORuntime): T = {
val promise = scala.concurrent.Promise[T]
io.unsafeRunAsync(v => promise.complete(v.toTry))
while(!promise.isCompleted) scala.scalanative.runtime.loop()
promise.future.value.get.get
}
}