Forked from keynmol/scala-http-postgres-html-docker.scala
Created
January 24, 2024 14:08
-
-
Save swuecho/7e9c3eb8f003085b6889df1fce788f1e to your computer and use it in GitHub Desktop.
Sample gist showing how to run a HTTP server with Typelevel Scala libraries, and a postgres docker container
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//> using dep "org.http4s::http4s-scalatags::0.25.2" | |
//> using dep "org.http4s::http4s-dsl::0.23.23" | |
//> using dep "org.http4s::http4s-ember-server::0.23.23" | |
//> using dep "org.tpolecat::skunk-core::0.6.0" | |
//> using dep "com.dimafeng::testcontainers-scala-postgresql::0.41.0" | |
//> using dep "com.outr::scribe-slf4j::3.12.2" | |
import skunk.*, codec.all.*, syntax.all.* | |
import cats.effect.* | |
import scalatags.Text.all.* | |
import org.http4s.{HttpRoutes} | |
import org.http4s.scalatags.* | |
import org.http4s.dsl.io.* | |
import org.http4s.ember.server.EmberServerBuilder | |
import com.comcast.ip4s.* | |
import com.dimafeng.testcontainers.PostgreSQLContainer | |
import org.testcontainers.utility.DockerImageName | |
import natchez.Trace | |
import cats.syntax.all.* | |
enum EmailAddress: | |
case Private | |
case Public(email: String) | |
def toOption = | |
this match | |
case Private => None | |
case Public(email) => Some(email) | |
object EmailAddress: | |
def from(s: Option[String]) = | |
s match | |
case None => EmailAddress.Private | |
case Some(value) => EmailAddress.Public(value) | |
case class User(id: Int, username: String, email: EmailAddress) | |
object User: | |
val codec = | |
(int4 *: varchar *: varchar.opt.imap(EmailAddress.from)(_.toOption)) | |
.to[User] | |
def main(a: scalatags.Text.Modifier*) = | |
html(body(a*)) | |
def queryUserById(id: Int)(using sess: Session[IO]) = | |
val query = sql"select * from users where id = $int4" | |
sess.option(query.query(User.codec))(id) | |
val userNotFoundPage = | |
p("This user does not exist :(") | |
def userView(user: User) = | |
main( | |
p(s"User ID: ${user.id}"), | |
p(s"Username: ${user.username}"), | |
user.email match | |
case EmailAddress.Private => p("This user has a private email address") | |
case EmailAddress.Public(email) => p(s"Email address: $email") | |
) | |
def renderUserOrNotFound(potentialUser: Option[User]) = | |
potentialUser.map(userView).getOrElse(userNotFoundPage) | |
def renderUserHandler(userId: Int)(using Session[IO]) = | |
queryUserById(userId).map(renderUserOrNotFound) | |
def handler(using Session[IO]) = | |
HttpRoutes.of[IO] { case GET -> Root / "users" / IntVar(i) => | |
renderUserHandler(i).flatMap(Ok(_)) | |
} | |
object App extends IOApp.Simple: | |
val run = | |
skunkConnection(using Trace.Implicits.noop) | |
.flatMap { case given Session[IO] => | |
EmberServerBuilder | |
.default[IO] | |
.withHttpApp(handler.orNotFound.onError { case err => | |
cats.data.Kleisli(req => IO(scribe.error(s"[$req] Error: ", err))) | |
}) | |
.withPort(port"9955") | |
.build | |
} | |
.use(server => | |
IO.println( | |
s"Server running at ${server.baseUri}, press Enter to terminate" | |
) *> IO.readLine | |
) | |
.void | |
private def postgresContainer = | |
Resource.make( | |
IO( | |
PostgreSQLContainer( | |
dockerImageNameOverride = DockerImageName("postgres:14"), | |
mountPostgresDataToTmpfs = true | |
) | |
).flatTap(cont => IO(cont.start())) | |
)(cont => IO(cont.stop())) | |
end postgresContainer | |
private def skunkConnection(using natchez.Trace[IO]) = | |
postgresContainer | |
.evalMap(cont => parseJDBC(cont.jdbcUrl).map(cont -> _)) | |
.flatMap { case (cont, jdbcUrl) => | |
Session.single[IO]( | |
host = jdbcUrl.getHost(), | |
port = jdbcUrl.getPort(), | |
user = cont.username, | |
password = Some(cont.password), | |
database = cont.databaseName | |
) | |
} | |
.evalTap { sess => | |
val commands = Seq( | |
sql"DROP TABLE IF EXISTS users".command, | |
sql"CREATE TABLE users (id serial, username varchar not null, email varchar)".command, | |
sql"INSERT INTO users(username, email) values ('anton', '[email protected]')".command, | |
sql"INSERT INTO users(username, email) values ('dark_anton', NULL)".command | |
) | |
commands.traverse(sess.execute) | |
} | |
private def parseJDBC(url: String) = IO(java.net.URI.create(url.substring(5))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment