removed DB module as well (useless)
continuous-integration/drone/push Build is failing Details

fixing problems occured with ZIO, the app now compiles
drone-setup
Override-6 2 years ago
parent 1650d472d1
commit 99f182502a

2
.gitignore vendored

@ -5,6 +5,8 @@ out
.bsp
keys
server.properties
log
*.sqlite

@ -0,0 +1,6 @@
database {
dataSourceClassName = org.sqlite.SQLiteDataSource
dataSource {
url = "jdbc:sqlite:database.sqlite;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:table_init.sql'"
}
}

@ -1,3 +1,5 @@
#may not be a public url
emitter.url=localhost:4454
eme=dzq=d=qzd=qz=d
emitter.url=<server url here>
emitter.cert=<x509 certificate path here>
endpoint.port=<enter port here>

@ -1,8 +1,8 @@
CREATE TABLE user
(
id varchar(32) PRIMARY KEY,
name varchar(30) NOT NULL,
mail_address varchar NOT NULL UNIQUE,
name varchar(30) NOT NULL,
forename varchar(30) NOT NULL,
password_hash varchar
);

@ -1,28 +0,0 @@
package org.tbasket
import io.getquill.{Literal, SqliteDialect}
import io.getquill.context.qzio.ZioContext
import org.tbasket.endpoint.Endpoint
import org.tbasket.handler.LoginHandler
import java.util.Properties
object EndpointSetup:
private final val EndpointPort = "endpoint.port"
private final val EndpointPortDefault = "48485"
def setupEndpoint(config: Properties): Endpoint =
Main.LOG.debug("Initializing API endpoint...")
createEndpoint(config)
private def createEndpoint(config: Properties): Endpoint =
val port = config
.getProperty(EndpointPort, EndpointPortDefault) match
case s"$port" if port.toIntOption.isDefined => port.toInt
case v =>
throw new InternalBasketServerException(
s"$EndpointPort property value is wrong: $v must be integer"
)
new Endpoint(port)

@ -1,33 +1,64 @@
package org.tbasket
import org.apache.logging.log4j.LogManager
import org.tbasket.db.Database
import org.tbasket.auth.Authentificator
import org.tbasket.data.Database
import org.tbasket.endpoint.Endpoint
import zio.*
import zio.http.URL
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import java.lang
import java.nio.file.{Files, Path}
import java.security.PublicKey
import java.security.cert.CertificateFactory
import java.util.Properties
import scala.io.StdIn
import scala.util.{Failure, Success}
import scala.util.control.NonFatal
import java.security.spec.{KeySpec, PKCS8EncodedKeySpec, RSAPrivateKeySpec, X509EncodedKeySpec}
object Main extends ZIOAppDefault:
final val LOG = LogManager.getLogger("Core")
final val LOG = LogManager.getLogger("Core")
override def run =
val config = retrieveConfig
val db = new Database(config)
EndpointSetup.setupEndpoint(config).run.provideLayer(db.layer)
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)
)
private def retrieveConfig: Properties =
private def setupEndpoint(config: ServerConfig) = ZIO.attempt {
new Endpoint(config.endpointPort)
}
private def setupDatabase(config: ServerConfig) = ZIO.attempt {
new Database(config)
}
private def setupAuth(config: ServerConfig) = ZIO.attempt {
val publicKey = config.emitterCertificate.getPublicKey
val auth = new Authentificator(config.emitterURL, publicKey, config.emitterCertificateAlgorithm)
ZLayer.succeed(auth)
}
private def retrieveConfig(args: ZIOAppArgs): Task[ServerConfig] = ZIO.attempt {
val configFile = Path.of("server.properties")
if Files.notExists(configFile) then
val in = getClass.getResourceAsStream("/server.properties")
Files.writeString(configFile, new String(in.readAllBytes()))
val in = Files.newInputStream(configFile)
val in = Files.newInputStream(configFile)
val properties = new Properties()
properties.load(in)
properties
}.flatMap(p => ServerConfig(p, args.getArgs))
// add a shutdown hook to log when the server is about to get killed
lang.Runtime.getRuntime.addShutdownHook(new Thread(() =>

@ -0,0 +1,83 @@
package org.tbasket
import org.tbasket.ServerConfig.CertFactory
import pdi.jwt.JwtAlgorithm
import zio.{Chunk, Task, ZIO, ZIOAppArgs}
import zio.http.URL
import zio.stream.ZStream
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import java.nio.file.{Files, Path}
import java.security.cert.{Certificate, CertificateFactory}
import java.util.Properties
class ServerConfig private(userProperties: Properties, schema: Properties, arguments: Map[String, String]) {
private def getPropertySafe(name: String) =
if (schema.getProperty(name) == null) {
throw new ServerConfigException(
s"""
| current config seems expired : property $name should not be present in your config
| (maybe this server has a newer version than the configuration and its was modified)
| config schema :
|
|$schemaString
|
| You may try to adapt your old config version with new schema
|""".stripMargin)
}
Option(userProperties.getProperty(name)) match
case Some(value) => value
case None =>
arguments.getOrElse(name, throw new ServerConfigException(
s"""
| could not find property in server configuration : $name
| config schema :
|
|$schemaString
|
| This property is required.
|""".stripMargin))
val emitterURL: URL = URL.fromString(getPropertySafe("emitter.url")) match
case Left(exception) => throw exception
case Right(value) => value
val emitterCertificate: Certificate = {
val path = Path.of(getPropertySafe("emitter.cert"))
val in = Files.newInputStream(path)
CertFactory.generateCertificate(in)
}
val emitterCertificateAlgorithm = JwtAlgorithm.RS256
val endpointPort: Int =
getPropertySafe("endpoint.port")
.toIntOption
.getOrElse(throw new ServerConfigException("endpoint.port is not an integer"))
private def schemaString = {
schema.stringPropertyNames()
.toArray(new Array[String](_))
.map(key => s"$key=${schema.getProperty(key)}")
.mkString("\n")
}
}
object ServerConfig {
//TODO make certificate type configurable
final val CertFactory = CertificateFactory.getInstance("X509")
def apply(userProperties: Properties, arguments: Seq[String]): Task[ServerConfig] = ZIO.attempt {
val args = arguments.map {
case s"$key=$value" => (key, value)
case x => throw new ServerConfigException(s"configuration parameter must be of format 'property.name=value' received : ${x}")
}.toMap
val schemaIn = getClass.getClassLoader.getResourceAsStream("server.properties")
val schema = new Properties()
schema.load(schemaIn)
new ServerConfig(userProperties, schema, args)
}
}

@ -0,0 +1,5 @@
package org.tbasket
class ServerConfigException(msg: String) extends Exception(msg) {
}

@ -6,8 +6,9 @@ import io.circe.parser.*
import io.circe.syntax.*
import io.getquill.*
import io.getquill.context.qzio.ZioJdbcContext
import io.getquill.context.sql.idiom.SqlIdiom
import org.tbasket.InternalBasketServerException
import org.tbasket.db.schemas.User
import org.tbasket.data.User
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import pdi.jwt.{JwtClaim, JwtZIOJson}
import zio.*
@ -66,9 +67,8 @@ class Authentificator(url: URL, key: PublicKey, algorithm: JwtAsymmetricAlgorith
.flatMap(ZIO.fromEither(_))
.map(_.uuid)
user <- ZIO.serviceWithZIO[ZioJdbcContext[_,_]] { ds =>
import org.tbasket.db.Database.ctx.*
user <- ZIO.serviceWithZIO[ZioJdbcContext[SqlIdiom, NamingStrategy]] { ctx =>
import ctx._
run(quote {
query[User].filter(_.id == lift(uuid))
}).map(_.headOption)

@ -0,0 +1,22 @@
package org.tbasket.data
import io.getquill.context.ZioJdbc.{DataSourceLayer, QuillZioExt}
import io.getquill.context.qzio.ZioContext
import io.getquill.idiom.Idiom
import io.getquill.jdbczio.Quill
import io.getquill.*
import org.sqlite.SQLiteDataSource
import org.tbasket.ServerConfig
import zio.*
import java.io.Closeable
import java.util.Properties
import javax.sql
class Database(config: ServerConfig):
val contextLayer = ZLayer.succeed(new SqliteZioJdbcContext(SnakeCase))
val datasourceLayer = Quill.DataSource.fromPrefix("database")

@ -0,0 +1,5 @@
package org.tbasket.data
import io.getquill.*
case class Member(team: Team, user: User, admin: Boolean)

@ -0,0 +1,6 @@
package org.tbasket.data
import io.getquill.*
case class Tactic(id: Int, name: String, owner: User, filePath: String)

@ -0,0 +1,5 @@
package org.tbasket.data
import io.getquill.*
case class Team(id: Int, name: String, clubName: String)

@ -1,7 +1,6 @@
package org.tbasket.db.schemas
package org.tbasket.data
import io.getquill.*
import org.tbasket.db.Database
import java.util.UUID

@ -1,7 +1,8 @@
package org.tbasket.endpoint
import io.getquill.{Literal, NamingStrategy, SqliteDialect}
import io.getquill.context.qzio.ZioContext
import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
import io.getquill.context.sql.idiom.SqlIdiom
import org.apache.logging.log4j.LogManager
import org.tbasket.auth.Authentificator
import org.tbasket.endpoint.Endpoint.LOG
@ -15,6 +16,7 @@ import zio.http.model.Status
import scala.collection.mutable
import zio.http.netty.client.ConnectionPool
import io.getquill.idiom.Idiom
import javax.sql.DataSource
class Endpoint(port: Int):
@ -47,7 +49,7 @@ class Endpoint(port: Int):
Server.install(app).flatMap { port =>
LOG.info(s"Listening API entries on $port")
ZIO.never
}.provideSome[ZioContext[Idiom, NamingStrategy] & Authentificator](
}.provideSome[ZioJdbcContext[SqlIdiom, NamingStrategy] & Authentificator & DataSource](
Scope.default,
serverConfigLayer,
ConnectionPool.fixed(4),

@ -1,14 +1,15 @@
package org.tbasket.handler
import io.getquill.*
import io.getquill.context.qzio.ZioContext
import io.getquill.context.ZioJdbc.*
import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
import io.getquill.context.sql.idiom.SqlIdiom
import org.tbasket.auth.Authentificator
import org.tbasket.db.Database.ctx.*
import org.tbasket.db.schemas.User
import org.tbasket.handler.HandlerUtils.errorBody
import org.tbasket.handler.LoginError.{InvalidRequest, *}
import org.tbasket.handler.LoginError.*
import org.tbasket.data.User
import zio.http.*
import zio.http.model.{Cookie, Header, Headers, Status}
import zio.http.{Body, Client, Request, Response, URL}
import zio.json.*
import zio.json.ast.Json.Str
import zio.json.ast.{Json, JsonCursor}
@ -16,8 +17,6 @@ import zio.{ZEnvironment, ZIO, *}
import java.sql.SQLException
import java.util.UUID
import io.getquill._
import io.getquill.context.ZioJdbc._
enum LoginError:
case TokenNotFound(token: UUID)
@ -30,25 +29,25 @@ enum LoginError:
object LoginHandler:
private def getUser(json: Json) =
ZIO.serviceWithZIO[SqliteZioJdbcContext[_]] { ctx =>
ZIO.serviceWithZIO[ZioJdbcContext[SqlIdiom, NamingStrategy]] { ctx =>
import ctx.*
for
mail <-
ZIO.fromEither(json.get[Str](JsonCursor.field("mail").isString).map(_.value))
.mapError(InvalidRequest("Missing or invalid field mail", _))
password <-
ZIO.fromEither(json.get[Str](JsonCursor.field("password").isString).map(_.value))
ZIO.fromEither(json.get[Str](JsonCursor.field("password").isString).map(_.value.hashCode))
.mapError(InvalidRequest("Missing or invalid field password", _))
result <- run(quote { // TODO use argon2id
result <- run(quote { // TODO use argon2id
query[User]
.filter(usr => usr.mailAddress == lift(mail))
.filter(usr => usr.passwordHash == lift(password.hashCode))
.filter(usr => usr.passwordHash == lift(password))
}).mapError(InternalError.apply)
yield result.headOption
}.someOrFail(InvalidPassword)
override def post(request: Request) =
def post(request: Request) =
val bindSession =
for
body <- request
@ -66,7 +65,7 @@ object LoginHandler:
jwt <- ZIO.serviceWithZIO[Authentificator](_.requestJwt(user))
yield (user, jwt)
bindSession.map { case (user, jwt) =>
bindSession.map { case (_, jwt) =>
Response(
status = Status.Found,
headers = Headers.location("/") ++ //login successful, go back to main page

@ -1 +0,0 @@
path=database.sqlite

@ -1,21 +0,0 @@
package org.tbasket.db
import io.getquill.context.ZioJdbc.{DataSourceLayer, QuillZioExt}
import io.getquill.context.qzio.ZioContext
import io.getquill.{Literal, SnakeCase, SqliteDialect, SqliteZioJdbcContext}
import org.sqlite.SQLiteDataSource
import zio.*
import java.io.Closeable
import java.util.Properties
import javax.sql
class Database(config: Properties):
val layer = ZLayer.succeed(new SqliteZioJdbcContext(SnakeCase))
object Database:
val ctx = new SqliteZioJdbcContext(SnakeCase)
import ctx.*

@ -1,19 +0,0 @@
package org.tbasket.db.schemas
import io.getquill.*
import org.tbasket.db.Database
case class Member(team: Team, user: User, admin: Boolean)
object Member:
import Database.ctx.*
val schema = quote {
querySchema[Member](
"member",
_.team.id -> "team_id",
_.user.id -> "user_id",
_.admin -> "is_admin"
)
}

@ -1,19 +0,0 @@
package org.tbasket.db.schemas
import io.getquill.*
import org.tbasket.db.Database
case class Tactic(id: Int, name: String, owner: User, filePath: String)
object Tactic:
import Database.ctx.*
val schema = quote {
querySchema[Tactic](
"tactic",
_.id -> "id",
_.name -> "name",
_.owner.id -> "owner_id",
_.filePath -> "file_path"
)
}

@ -1,17 +0,0 @@
package org.tbasket.db.schemas
import io.getquill.*
case class Team(id: Int, name: String, clubName: String)
object Team:
import org.tbasket.db.Database.ctx.*
val schema = quote {
querySchema[Team](
"team",
_.id -> "id",
_.name -> "name",
_.clubName -> "club_name"
)
}

@ -38,18 +38,9 @@ object Core extends HttpModule { //also handles http
ivy"io.circe::circe-core:0.15.0-M1",
ivy"io.circe::circe-parser:0.14.3",
ivy"io.circe::circe-generic:0.14.3",
)
override def moduleDeps = Seq(DB)
}
/**
* Database module
* */
object DB extends ServerModule {
override def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.getquill::quill-jdbc-zio:4.6.0",
ivy"org.xerial:sqlite-jdbc:3.40.0.0",
)
}