From b22a1230c74059a2b8925bec5d1e035d090d4890 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Fri, 3 Feb 2023 21:19:33 +0100 Subject: [PATCH] simple page dispatcher --- .gitignore | 5 +- Core/resources/server.properties | 4 +- Core/src/org/tbasket/Main.scala | 21 ++++---- .../org/tbasket/config/FileServerConfig.scala | 3 ++ .../src/org/tbasket/config/ServerConfig.scala | 3 ++ .../org/tbasket/dispatch/PageDispatcher.scala | 53 +++++++++++++++++++ Core/src/org/tbasket/endpoint/Endpoint.scala | 9 +++- .../org/tbasket/test/TestServerConfig.scala | 3 ++ .../test/pages/RegisterPageHandlerTests.scala | 21 ++++++-- 9 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 Core/src/org/tbasket/dispatch/PageDispatcher.scala diff --git a/.gitignore b/.gitignore index c90b89a..d7ef6d1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,12 @@ out target */.gradle/ .bsp -keys +keys +www +log server.properties -log *.sqlite *.$* diff --git a/Core/resources/server.properties b/Core/resources/server.properties index 8ab616d..2a1d6be 100644 --- a/Core/resources/server.properties +++ b/Core/resources/server.properties @@ -4,4 +4,6 @@ emitter.cert= endpoint.port= -database.prefix=database +database.prefix= + +pages.location= diff --git a/Core/src/org/tbasket/Main.scala b/Core/src/org/tbasket/Main.scala index ebf2087..778b55d 100644 --- a/Core/src/org/tbasket/Main.scala +++ b/Core/src/org/tbasket/Main.scala @@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager import org.tbasket.auth.Authenticator import org.tbasket.config.{FileServerConfig, ServerConfig} import org.tbasket.data.Database +import org.tbasket.dispatch.PageDispatcher import org.tbasket.endpoint.Endpoint import pdi.jwt.algorithms.JwtAsymmetricAlgorithm import zio.* @@ -23,16 +24,18 @@ object Main extends ZIOAppDefault: final val LOG = LogManager.getLogger("Core") override def run = ZIO.serviceWithZIO[ZIOAppArgs] { args => - for - config <- retrieveConfig(args) - res <- setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config) - yield - val (auth, db, ep) = res - (auth, db, ep) - }.flatMap((auth, db, ep) => - ep.run.provide(db.datasourceLayer, db.contextLayer, auth) - ) + retrieveConfig(args) + .flatMap(config => setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config) <&> setupPageDispatcher(config)) + .flatMap { + case (auth, db, ep, disp) => + ep.run.provide(db.datasourceLayer, db.contextLayer, auth, disp) + } + } + private def setupPageDispatcher(config: ServerConfig) = ZIO.attempt { + val dispatcher = new PageDispatcher(config.pagesLocation) + ZLayer.succeed(dispatcher) + } private def setupEndpoint(config: ServerConfig) = ZIO.attempt { new Endpoint(config.endpointPort) diff --git a/Core/src/org/tbasket/config/FileServerConfig.scala b/Core/src/org/tbasket/config/FileServerConfig.scala index 560b08b..2045802 100644 --- a/Core/src/org/tbasket/config/FileServerConfig.scala +++ b/Core/src/org/tbasket/config/FileServerConfig.scala @@ -62,6 +62,9 @@ final class FileServerConfig private(userProperties: Properties, schema: Propert override val databaseConfigName: String = getPropertySafe("database.prefix") + + override def pagesLocation: Path = Path.of(getPropertySafe("pages.location")) + private def schemaString = { schema.stringPropertyNames() .toArray(new Array[String](_)) diff --git a/Core/src/org/tbasket/config/ServerConfig.scala b/Core/src/org/tbasket/config/ServerConfig.scala index ac32741..11b7690 100644 --- a/Core/src/org/tbasket/config/ServerConfig.scala +++ b/Core/src/org/tbasket/config/ServerConfig.scala @@ -4,6 +4,7 @@ import pdi.jwt.JwtAlgorithm import pdi.jwt.algorithms.JwtAsymmetricAlgorithm import zio.http.URL +import java.nio.file.Path import java.security.cert.Certificate trait ServerConfig { @@ -17,4 +18,6 @@ trait ServerConfig { def databaseConfigName: String + def pagesLocation: Path + } diff --git a/Core/src/org/tbasket/dispatch/PageDispatcher.scala b/Core/src/org/tbasket/dispatch/PageDispatcher.scala new file mode 100644 index 0000000..ec3abf8 --- /dev/null +++ b/Core/src/org/tbasket/dispatch/PageDispatcher.scala @@ -0,0 +1,53 @@ +package org.tbasket.dispatch + +import zio.* +import zio.http.* +import zio.http.model.Status +import zio.http.model.Status.* +import zio.stream.ZStream + +import java.nio.file.{Files, Path} +import scala.collection.mutable + +class PageDispatcher(pagesLocation: Path) { + + private val resources = resolveResources + + + def send(r: Request) = + ZIO.attempt(pagesLocation.toString + r.url.path.toString) + .filterOrFail(!_.startsWith("/"))(Forbidden) + .map(resources.get) + .someOrFail(NotFound) + .map(content => Response(status = Ok, body = content)) + .catchSome { + case status: Status => + ZIO.attempt(Response.status(status)) + } + + + private def resolveResources: Map[String, Body] = { + + val map = mutable.HashMap.empty[String, Body] + + def resolveAll(loc: Path): Unit = { + Files.list(loc) + .forEach { + case d if Files.isDirectory(d) => resolveAll(d) + case f => + val body = Body.fromStream(ZStream.fromPath(f)) + val fileName = f.toString + map.put(fileName, body) + val extension = fileName.drop(fileName.indexOf('.')) + + val dirPath = f.getParent.toString + if (extension == ".html") + map.put(dirPath, body) //also bind the dir path with the index body + else map.put(dirPath + extension, body) + } + } + + resolveAll(pagesLocation) + map.toMap + } +} diff --git a/Core/src/org/tbasket/endpoint/Endpoint.scala b/Core/src/org/tbasket/endpoint/Endpoint.scala index d270b95..be1c9aa 100644 --- a/Core/src/org/tbasket/endpoint/Endpoint.scala +++ b/Core/src/org/tbasket/endpoint/Endpoint.scala @@ -4,9 +4,11 @@ import io.getquill.context.qzio.{ZioContext, ZioJdbcContext} import io.getquill.context.sql.idiom.SqlIdiom import io.getquill.idiom.Idiom import io.getquill.{Literal, NamingStrategy, SqliteDialect} +import io.netty.handler.codec.http.HttpMethod import org.apache.logging.log4j.{LogManager, Logger} import org.tbasket.auth.Authenticator import org.tbasket.data.DatabaseContext +import org.tbasket.dispatch.PageDispatcher import org.tbasket.endpoint.Endpoint.{Log, app} import org.tbasket.error.* import org.tbasket.handler.HandlerUtils.errorBody @@ -35,7 +37,7 @@ class Endpoint(port: Int): Server.install(app).flatMap { port => Log.info(s"Listening API entries on $port") ZIO.never - }.provideSome[DatabaseContext & Authenticator & DataSource]( + }.provideSome[DatabaseContext & Authenticator & DataSource & PageDispatcher]( Scope.default, serverConfigLayer, ConnectionPool.fixed(4), @@ -50,10 +52,13 @@ object Endpoint: private def tryHandle(r: Request) = r match case r@POST -> _ / "login" => LoginPageHandler.post(r) - + case r@POST -> _ / "register" => RegisterPageHandler.post(r) + case r@GET -> _ => + ZIO.serviceWithZIO[PageDispatcher](_.send(r)) + case r@method -> path => val ipInsights = r.remoteAddress .map(ip => s": request received from $ip.") diff --git a/tests/src/org/tbasket/test/TestServerConfig.scala b/tests/src/org/tbasket/test/TestServerConfig.scala index 6fa64aa..f3f3f36 100644 --- a/tests/src/org/tbasket/test/TestServerConfig.scala +++ b/tests/src/org/tbasket/test/TestServerConfig.scala @@ -8,6 +8,7 @@ import pdi.jwt.algorithms.JwtAsymmetricAlgorithm import java.nio.file.{Files, Path} import java.security.cert.{Certificate, CertificateFactory} +import scala.reflect.io object TestServerConfig extends ServerConfig { new ProcessBuilder("bash", "./tests/resources/generate_keys.sh") @@ -27,4 +28,6 @@ object TestServerConfig extends ServerConfig { override def endpointPort: Int = 5454 override def databaseConfigName: String = "test-database" + + override def pagesLocation: io.Path = Path.of("www") } diff --git a/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala b/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala index 6a074f4..26e707b 100644 --- a/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala +++ b/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala @@ -1,11 +1,11 @@ package org.tbasket.test.pages -import zio.test.ZIOSpecDefault import org.tbasket.endpoint.Endpoint.handle import org.tbasket.handler.HandlerUtils.parseAttribute import org.tbasket.test.TestUtils import org.tbasket.test.TestUtils.getJsonBody import org.tbasket.test.pages.RegisterPageHandlerTests.test +import zio.* import zio.http.* import zio.http.model.{HeaderNames, Headers, Status} import zio.json.ast.JsonCursor @@ -16,7 +16,22 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") { private def requestsSpec = suite("bad request tests")( - + ZIO.attempt(Map( + "empty packet" -> Body.empty, + "with no mail attribute" -> Body.fromString("""{"password":"1234"}"""), + "with no password attribute" -> Body.fromString("""{"email":"valid.mail@x.y"}"""), + "with invalid json" -> Body.fromString("""this is a corrupted json""") + )).map(_.map { case (name, body) => + test(name) { + for + response <- handle(Request.post(body, url)) + json <- getJsonBody(response) + errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) + yield + assert(response)(hasField("status", _.status, equalTo(Status.BadRequest))) + && assertTrue(errorType.startsWith("invalid")) + } + }) ) private def registerSpec = suite("register tests")( @@ -35,7 +50,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") { resp <- handle(Request.post(Body.fromString(s"""{"name":"tuaillon","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"bouhours"}"""), url)) json <- getJsonBody(resp) errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) - + yield assert(resp)(hasField("status", _.status, equalTo(Status.NotAcceptable))) && assert(errorType)(equalTo("already registered"))