diff --git a/build.mill b/build.mill index 7da1905..6ab22a6 100644 --- a/build.mill +++ b/build.mill @@ -27,7 +27,10 @@ object summercms extends ScalaModule { // Flyway for migrations mvn"org.flywaydb:flyway-core:10.21.0", - mvn"org.flywaydb:flyway-database-postgresql:10.21.0" + mvn"org.flywaydb:flyway-database-postgresql:10.21.0", + + // Logging + mvn"org.slf4j:slf4j-simple:2.0.9" ) def scalacOptions = Seq( diff --git a/favico.jpg b/summercms/resources/public/assets/images/favico.jpg similarity index 100% rename from favico.jpg rename to summercms/resources/public/assets/images/favico.jpg diff --git a/summercms/resources/public/assets/images/logo.png b/summercms/resources/public/assets/images/logo.png new file mode 100644 index 0000000..0e40e16 Binary files /dev/null and b/summercms/resources/public/assets/images/logo.png differ diff --git a/summercms.jpg b/summercms/resources/public/assets/images/summercms.jpg similarity index 100% rename from summercms.jpg rename to summercms/resources/public/assets/images/summercms.jpg diff --git a/summercms/resources/public/landing.html b/summercms/resources/public/landing.html new file mode 100644 index 0000000..50b2453 --- /dev/null +++ b/summercms/resources/public/landing.html @@ -0,0 +1,216 @@ + + + + + + + SummerCMS + + + + + + + + + + + + + + + + + + + +
+ +

SUMMERCMS

+

A New Dawn in Content Management

+
+ + Server Running +
+ +
+
SummerCMS v0.1.0 | Scala + ZIO
+ + diff --git a/summercms/src/Main.scala b/summercms/src/Main.scala index 9b7afa1..280fd47 100644 --- a/summercms/src/Main.scala +++ b/summercms/src/Main.scala @@ -10,15 +10,18 @@ object Main extends ZIOAppDefault { private val banner: String = """ - | . - | \ | / - | '-.ooooo.-' - | --- ooooo --- - | .-'ooooo'-. - | / | \ - | ' | - | S U M M E R C M S + | | . + | `. * | .' + | `. ._|_* .' . + | . * .' `. * + | -------| |------- + | . *`.___.' * . + | .' |* `. * + | .' * | . `. + | . | + | + | S U M M E R C M S |""".stripMargin override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = @@ -28,7 +31,7 @@ object Main extends ZIOAppDefault { for { cfg <- ZIO.config[SummerConfig](SummerConfig.config) _ <- Console.printLine(banner) - _ <- Console.printLine(s" Starting on port ${cfg.server.port}...") + _ <- Console.printLine(s"Starting on port ${cfg.server.port}...") _ <- Console.printLine("") // Note: Migrations are NOT auto-run. Use CLI to run migrations (Phase 5). _ <- Server.serve(Routes.routes).provide( diff --git a/summercms/src/api/LandingRoutes.scala b/summercms/src/api/LandingRoutes.scala new file mode 100644 index 0000000..1d4cbe0 --- /dev/null +++ b/summercms/src/api/LandingRoutes.scala @@ -0,0 +1,62 @@ +package api + +import zio.* +import zio.http.* +import scala.io.Source + +/** Landing page and static asset routes + */ +object LandingRoutes { + + private def loadResource(path: String): Option[String] = + Option(getClass.getClassLoader.getResourceAsStream(path)).map { stream => + val content = Source.fromInputStream(stream, "UTF-8").mkString + stream.close() + content + } + + private def serveLanding: ZIO[Any, Nothing, Response] = + ZIO.succeed { + loadResource("public/landing.html") match { + case Some(html) => + Response( + status = Status.Ok, + headers = Headers(Header.ContentType(MediaType.text.html)), + body = Body.fromString(html) + ) + case None => + Response.text("Landing page not found").status(Status.InternalServerError) + } + } + + private def serveImage(filename: String): ZIO[Any, Nothing, Response] = + ZIO.attemptBlocking { + val path = s"public/assets/images/$filename" + val stream = getClass.getClassLoader.getResourceAsStream(path) + if (stream == null) { + Response.notFound + } else { + val bytes = stream.readAllBytes() + stream.close() + val mediaType = if (filename.endsWith(".png")) MediaType.image.png + else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) MediaType.image.jpeg + else if (filename.endsWith(".svg")) MediaType.image.`svg+xml` + else if (filename.endsWith(".gif")) MediaType.image.gif + else MediaType.application.`octet-stream` + Response( + status = Status.Ok, + headers = Headers(Header.ContentType(mediaType)), + body = Body.fromChunk(Chunk.fromArray(bytes)) + ) + } + }.catchAll(_ => ZIO.succeed(Response.notFound)) + + val routes: Routes[Any, Response] = + Routes( + Method.GET / Root -> handler(serveLanding), + Method.GET / "assets" / "images" / string("filename") -> handler { (filename: String, _: Request) => + serveImage(filename) + } + ) + +} diff --git a/summercms/src/api/Routes.scala b/summercms/src/api/Routes.scala index bb15855..28fc81a 100644 --- a/summercms/src/api/Routes.scala +++ b/summercms/src/api/Routes.scala @@ -10,6 +10,6 @@ import javax.sql.DataSource object Routes { val routes: Routes[DataSource, Response] = - HealthRoutes.routes + LandingRoutes.routes ++ HealthRoutes.routes }