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 revolver #934

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ Docker-compose also starts up a local nginx server that serves an example client

- http://local.lucuma.xyz:8081/playground.html

### Using reStart

Alternatively, you can run the app from within SBT with `service/reStart`
(stopping with `service/reStop`). By default, this command will fail after
running `docker-compose` `down` and then `up` as described above. You can
supply optional arguments to simplify development though:

* `--reset` - Drops then creates the database for you. Do this after cycling
`docker-compose` `down`, `up` to give flyway a chance to run the migration and
update its schema table.
* `--skip-migration` - Skips the database migration. This assumes that the
database has been initialized already. Usually this won't be necessary since
flyway already skips migrations that have previously run.

### Working on the Schema

The app runs the migrations in `/modules/service/src/main/resources/db/migration` on startup.
Expand All @@ -189,7 +203,7 @@ You can connect to youe dev database with locally-installed tools like `pgAdmin`

If you try to run `Main` you will find that it barfs because it needs some ORCID configuration. To set this up, sign into [ORCID](http://orcid.org) as yourself, go to **Developer Tools** under the name menu and create an API key with redirect URL `http://localhost:8080/auth/v1/stage2`. This will allow you to test ORCID authentication locally.

You will need to provide `GPP_ORCID_CLIENT_ID` and `GPP_ORCID_CLIENT_SECRET` either as environment variables or system properties when you run `Main`. To do this in VS-Code you can hit F5 and then set up a run configuration as follows after which hitting F5 again should run SSO locally. Output will be in the Debug Console window.
You will need to provide `LUCUMA_ORCID_CLIENT_ID` and `LUCUMA_ORCID_CLIENT_SECRET` either as environment variables or system properties when you run `Main`. To do this in VS-Code you can hit F5 and then set up a run configuration as follows after which hitting F5 again should run SSO locally. Output will be in the Debug Console window.

```json
{
Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ lazy val service = project
"com.disneystreaming" %% "weaver-cats" % weaverVersion % Test,
"com.disneystreaming" %% "weaver-scalacheck" % weaverVersion % Test,
),
reStart / envVars += "PORT" -> "8082",
reStartArgs += "serve"
)

lazy val backendExample = project
Expand Down
78 changes: 71 additions & 7 deletions modules/service/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ import skunk.{Command as _, *}
import scala.concurrent.duration.*
import scala.io.AnsiColor

object MainArgs:
opaque type ResetDatabase = Boolean

object ResetDatabase:
val opt: Opts[ResetDatabase] =
Opts.flag("reset", help = "Drop and recreate the database before starting.").orFalse

extension (rd: ResetDatabase)
def toBoolean: Boolean = rd
def isRequested: Boolean = toBoolean

opaque type SkipMigration = Boolean

object SkipMigration:
val opt: Opts[SkipMigration] =
Opts.flag("skip-migration", help = "Skip database migration on startup.").orFalse

extension (sm: SkipMigration)
def toBoolean: Boolean = sm
def isRequested: Boolean = toBoolean

object Main extends CommandIOApp(
name = "lucuma-sso",
header =
Expand All @@ -55,6 +76,8 @@ object Main extends CommandIOApp(
|""".stripMargin,
) {

import MainArgs.*

def main: Opts[IO[ExitCode]] =
command

Expand All @@ -65,7 +88,13 @@ object Main extends CommandIOApp(
Command(
name = "serve",
header = "Run the SSO service.",
)(FMain.serve.pure[Opts])
)((ResetDatabase.opt, SkipMigration.opt).tupled.map { case (reset, skipMigration) =>
for
_ <- IO.whenA(reset.isRequested)(IO.println("Resetting database."))
_ <- IO.whenA(skipMigration.isRequested)(IO.println("Skipping migration. Ensure that your database is up-to-date."))
e <- FMain.serve(reset, skipMigration)
yield e
})

lazy val createServiceUser =
Command(
Expand Down Expand Up @@ -216,23 +245,58 @@ object FMain extends AnsiColor {
implicit def kleisliLogger[F[_]: Logger, A]: Logger[Kleisli[F, A, *]] =
Logger[F].mapK(Kleisli.liftK)

def singleSession[F[_]: Async: Console: Network](
config: DatabaseConfig,
database: Option[String] = None
): Resource[F, Session[F]] =
import natchez.Trace.Implicits.noop
Session.single[F](
host = config.host,
port = config.port,
user = config.user,
database = database.getOrElse(config.database),
password = config.password,
ssl = SSL.Trusted.withFallback(true),
strategy = Strategy.SearchPath
)

def resetDatabase[F[_]: Async : Console : Network](config: DatabaseConfig): F[Unit] =
import skunk.*
import skunk.implicits.*

val drop = sql"""DROP DATABASE "#${config.database}"""".command
val create = sql"""CREATE DATABASE "#${config.database}"""".command

singleSession(config, "postgres".some).use: s =>
for
_ <- s.execute(drop).void
_ <- s.execute(create).void
yield()

/**
* Our main server, as a resource that starts up our server on acquire and shuts it all down
* in cleanup, yielding an `ExitCode`. Users will `use` this resource and hold it forever.
*/
def server(using Logger[IO]): Resource[IO, ExitCode] =
for {
def server(
reset: MainArgs.ResetDatabase,
skipMigration: MainArgs.SkipMigration
)(using Logger[IO]): Resource[IO, ExitCode] =
for
c <- Resource.eval(Config.config.load[IO])
_ <- Resource.eval(banner[IO](c))
_ <- Resource.eval(migrateDatabase[IO](c.database))
_ <- Applicative[Resource[IO, *]].whenA(reset.isRequested)(Resource.eval(resetDatabase[IO](c.database)))
_ <- Applicative[Resource[IO, *]].unlessA(skipMigration.isRequested)(Resource.eval(migrateDatabase[IO](c.database)))
ep <- entryPointResource[IO](c.honeycomb)
ap <- ep.wsLiftR(routesResource(c)).map(_.map(_.orNotFound))
_ <- serverResource(c.httpPort, ap)
} yield ExitCode.Success
yield ExitCode.Success

/** Our main server, which runs forever. */
def serve(using Logger[IO]): IO[ExitCode] =
server.useForever
def serve(
reset: MainArgs.ResetDatabase,
skipMigration: MainArgs.SkipMigration
)(using Logger[IO]): IO[ExitCode] =
server(reset, skipMigration).useForever

/** Standalone single-use database instance for one-off commands. */
def standaloneDatabase[F[_]: Temporal: Network: Console](
Expand Down
9 changes: 5 additions & 4 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
addSbtPlugin("edu.gemini" % "sbt-lucuma-lib" % "0.12.1")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0")
addSbtPlugin("edu.gemini" % "sbt-lucuma-lib" % "0.12.1")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
Loading