simple page dispatcher
continuous-integration/drone/push Build is failing Details

dev
Override-6 2 years ago
parent c9f45d1bf4
commit b22a1230c7

5
.gitignore vendored

@ -4,11 +4,12 @@ out
target target
*/.gradle/ */.gradle/
.bsp .bsp
keys keys
www
log
server.properties server.properties
log
*.sqlite *.sqlite
*.$* *.$*

@ -4,4 +4,6 @@ emitter.cert=<x509 certificate path here>
endpoint.port=<enter port here> endpoint.port=<enter port here>
database.prefix=database database.prefix=<database config prefix>
pages.location=<location to html content>

@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager
import org.tbasket.auth.Authenticator import org.tbasket.auth.Authenticator
import org.tbasket.config.{FileServerConfig, ServerConfig} import org.tbasket.config.{FileServerConfig, ServerConfig}
import org.tbasket.data.Database import org.tbasket.data.Database
import org.tbasket.dispatch.PageDispatcher
import org.tbasket.endpoint.Endpoint import org.tbasket.endpoint.Endpoint
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import zio.* import zio.*
@ -23,16 +24,18 @@ object Main extends ZIOAppDefault:
final val LOG = LogManager.getLogger("Core") final val LOG = LogManager.getLogger("Core")
override def run = ZIO.serviceWithZIO[ZIOAppArgs] { args => override def run = ZIO.serviceWithZIO[ZIOAppArgs] { args =>
for retrieveConfig(args)
config <- retrieveConfig(args) .flatMap(config => setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config) <&> setupPageDispatcher(config))
res <- setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config) .flatMap {
yield case (auth, db, ep, disp) =>
val (auth, db, ep) = res ep.run.provide(db.datasourceLayer, db.contextLayer, auth, disp)
(auth, db, ep) }
}.flatMap((auth, db, ep) => }
ep.run.provide(db.datasourceLayer, db.contextLayer, auth)
)
private def setupPageDispatcher(config: ServerConfig) = ZIO.attempt {
val dispatcher = new PageDispatcher(config.pagesLocation)
ZLayer.succeed(dispatcher)
}
private def setupEndpoint(config: ServerConfig) = ZIO.attempt { private def setupEndpoint(config: ServerConfig) = ZIO.attempt {
new Endpoint(config.endpointPort) new Endpoint(config.endpointPort)

@ -62,6 +62,9 @@ final class FileServerConfig private(userProperties: Properties, schema: Propert
override val databaseConfigName: String = getPropertySafe("database.prefix") override val databaseConfigName: String = getPropertySafe("database.prefix")
override def pagesLocation: Path = Path.of(getPropertySafe("pages.location"))
private def schemaString = { private def schemaString = {
schema.stringPropertyNames() schema.stringPropertyNames()
.toArray(new Array[String](_)) .toArray(new Array[String](_))

@ -4,6 +4,7 @@ import pdi.jwt.JwtAlgorithm
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import zio.http.URL import zio.http.URL
import java.nio.file.Path
import java.security.cert.Certificate import java.security.cert.Certificate
trait ServerConfig { trait ServerConfig {
@ -17,4 +18,6 @@ trait ServerConfig {
def databaseConfigName: String def databaseConfigName: String
def pagesLocation: Path
} }

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

@ -4,9 +4,11 @@ import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
import io.getquill.context.sql.idiom.SqlIdiom import io.getquill.context.sql.idiom.SqlIdiom
import io.getquill.idiom.Idiom import io.getquill.idiom.Idiom
import io.getquill.{Literal, NamingStrategy, SqliteDialect} import io.getquill.{Literal, NamingStrategy, SqliteDialect}
import io.netty.handler.codec.http.HttpMethod
import org.apache.logging.log4j.{LogManager, Logger} import org.apache.logging.log4j.{LogManager, Logger}
import org.tbasket.auth.Authenticator import org.tbasket.auth.Authenticator
import org.tbasket.data.DatabaseContext import org.tbasket.data.DatabaseContext
import org.tbasket.dispatch.PageDispatcher
import org.tbasket.endpoint.Endpoint.{Log, app} import org.tbasket.endpoint.Endpoint.{Log, app}
import org.tbasket.error.* import org.tbasket.error.*
import org.tbasket.handler.HandlerUtils.errorBody import org.tbasket.handler.HandlerUtils.errorBody
@ -35,7 +37,7 @@ class Endpoint(port: Int):
Server.install(app).flatMap { port => Server.install(app).flatMap { port =>
Log.info(s"Listening API entries on $port") Log.info(s"Listening API entries on $port")
ZIO.never ZIO.never
}.provideSome[DatabaseContext & Authenticator & DataSource]( }.provideSome[DatabaseContext & Authenticator & DataSource & PageDispatcher](
Scope.default, Scope.default,
serverConfigLayer, serverConfigLayer,
ConnectionPool.fixed(4), ConnectionPool.fixed(4),
@ -50,10 +52,13 @@ object Endpoint:
private def tryHandle(r: Request) = r match private def tryHandle(r: Request) = r match
case r@POST -> _ / "login" => case r@POST -> _ / "login" =>
LoginPageHandler.post(r) LoginPageHandler.post(r)
case r@POST -> _ / "register" => case r@POST -> _ / "register" =>
RegisterPageHandler.post(r) RegisterPageHandler.post(r)
case r@GET -> _ =>
ZIO.serviceWithZIO[PageDispatcher](_.send(r))
case r@method -> path => case r@method -> path =>
val ipInsights = r.remoteAddress val ipInsights = r.remoteAddress
.map(ip => s": request received from $ip.") .map(ip => s": request received from $ip.")

@ -8,6 +8,7 @@ import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import java.security.cert.{Certificate, CertificateFactory} import java.security.cert.{Certificate, CertificateFactory}
import scala.reflect.io
object TestServerConfig extends ServerConfig { object TestServerConfig extends ServerConfig {
new ProcessBuilder("bash", "./tests/resources/generate_keys.sh") new ProcessBuilder("bash", "./tests/resources/generate_keys.sh")
@ -27,4 +28,6 @@ object TestServerConfig extends ServerConfig {
override def endpointPort: Int = 5454 override def endpointPort: Int = 5454
override def databaseConfigName: String = "test-database" override def databaseConfigName: String = "test-database"
override def pagesLocation: io.Path = Path.of("www")
} }

@ -1,11 +1,11 @@
package org.tbasket.test.pages package org.tbasket.test.pages
import zio.test.ZIOSpecDefault
import org.tbasket.endpoint.Endpoint.handle import org.tbasket.endpoint.Endpoint.handle
import org.tbasket.handler.HandlerUtils.parseAttribute import org.tbasket.handler.HandlerUtils.parseAttribute
import org.tbasket.test.TestUtils import org.tbasket.test.TestUtils
import org.tbasket.test.TestUtils.getJsonBody import org.tbasket.test.TestUtils.getJsonBody
import org.tbasket.test.pages.RegisterPageHandlerTests.test import org.tbasket.test.pages.RegisterPageHandlerTests.test
import zio.*
import zio.http.* import zio.http.*
import zio.http.model.{HeaderNames, Headers, Status} import zio.http.model.{HeaderNames, Headers, Status}
import zio.json.ast.JsonCursor import zio.json.ast.JsonCursor
@ -16,7 +16,22 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
private def requestsSpec = suite("bad request tests")( 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")( 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)) resp <- handle(Request.post(Body.fromString(s"""{"name":"tuaillon","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"bouhours"}"""), url))
json <- getJsonBody(resp) json <- getJsonBody(resp)
errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString)
yield yield
assert(resp)(hasField("status", _.status, equalTo(Status.NotAcceptable))) assert(resp)(hasField("status", _.status, equalTo(Status.NotAcceptable)))
&& assert(errorType)(equalTo("already registered")) && assert(errorType)(equalTo("already registered"))