feat(01): Simple initial landing

This commit is contained in:
Jakub Zych
2026-02-05 13:01:34 +01:00
parent d502bb19a5
commit 7b5fe94e53
8 changed files with 295 additions and 11 deletions

View File

@@ -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(

View File

@@ -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)
}
)
}

View File

@@ -10,6 +10,6 @@ import javax.sql.DataSource
object Routes {
val routes: Routes[DataSource, Response] =
HealthRoutes.routes
LandingRoutes.routes ++ HealthRoutes.routes
}