working on backend
continuous-integration/drone/push Build is failing Details

drone-setup
Override-6 2 years ago
parent ebcbc8bab7
commit 27b808abe3

4
.gitignore vendored

@ -1,8 +1,10 @@
build
.gradle
.idea
*.sqlite
*/.gradle/
log
*.sqlite
.$*

@ -11,8 +11,7 @@ import zio.http.model.Status
import scala.collection.mutable
class Endpoint(port: Int) extends ZIOAppDefault {
class Endpoint(port: Int) {
private val handlers = mutable.HashMap.empty[String, APIRequestHandler]
@ -26,17 +25,17 @@ class Endpoint(port: Int) extends ZIOAppDefault {
}
private val app = Http.collect[Request] {
case r@GET -> _ / path if handlers.contains(path) =>
transform(handlers(path).get(r))
case r@POST -> _ / path if handlers.contains(path) =>
transform(handlers(path).post(r))
private val app = Http.collectZIO[Request] {
case GET -> _ / path if handlers.contains(path) =>
handlers(path).get.map(transform)
case POST -> _ / path if handlers.contains(path) =>
handlers(path).post.map(transform)
case r@method -> path =>
val ipInsights = r.remoteAddress
.map(ip => s": request received from $ip.")
.getOrElse("")
LOG.error(s"Was unable to find a handler for request '$path' with method $method ${ipInsights}")
Response(Status.NotFound)
ZIO.succeed(Response(Status.NotFound))
}
val run = {
@ -48,7 +47,7 @@ class Endpoint(port: Int) extends ZIOAppDefault {
Server.install(app).flatMap { port =>
LOG.info(s"Listening API entries on $port")
ZIO.never
}.provide(configLayer, Server.live)
}.provideSome(configLayer, Server.live)
}
}

@ -1,12 +1,13 @@
package org.tbasket.api.compute
import zio.ZIO
import zio.http.model.Status
import zio.http.{Request, Response}
trait APIRequestHandler {
def get(request: Request): Response = Response(Status.MethodNotAllowed)
def get: ZIO[Request, Throwable, Response] = ZIO.succeed(Response(Status.MethodNotAllowed))
def post(request: Request): Response = Response(Status.MethodNotAllowed)
def post: ZIO[Request, Throwable, Response] = ZIO.succeed(Response(Status.MethodNotAllowed))
}

@ -1,5 +1,9 @@
group "org.tbasket.api"
dependencies {
implementation 'io.getquill:quill_2.12:3.2.0'
implementation 'org.xerial:sqlite-jdbc:3.40.0.0'
implementation 'io.getquill:quill-jdbc-zio_2.13:3.9.0'
implementation 'io.getquill:quill-zio_2.13:3.9.0'
}

@ -0,0 +1,44 @@
CREATE TABLE user
(
id int PRIMARY KEY,
name varchar(30) NOT NULL,
mail_address varchar NOT NULL UNIQUE,
forename varchar(30) NOT NULL,
password_hash varchar
);
CREATE TABLE team
(
id int,
name varchar(30),
club_name varchar(30),
PRIMARY KEY (id)
);
CREATE TABLE tactic_group
(
tactic_id int,
team_id int,
PRIMARY KEY (tactic_id, team_id),
FOREIGN KEY (tactic_id) REFERENCES tactic (id),
FOREIGN KEY (team_id) REFERENCES team (id)
);
CREATE TABLE member
(
team_id int,
user_id int,
is_admin boolean NOT NULL,
PRIMARY KEY (team_id, user_id),
FOREIGN KEY (team_id) REFERENCES team (id),
FOREIGN KEY (user_id) REFERENCES user (id)
);
CREATE TABLE tactic
(
id int PRIMARY KEY,
name varchar(30) NOT NULL,
owner_id int,
file_path varchar NOT NULL,
FOREIGN KEY (owner_id) REFERENCES user (id)
);

@ -0,0 +1,46 @@
package org.tbasket.db
import io.getquill.context.ZioJdbc.{DataSourceLayer, QuillZioExt}
import io.getquill.{Literal, SqliteZioJdbcContext}
import org.sqlite.SQLiteDataSource
import org.tbasket.db.schemas.User
import zio._
import zio.console._
import java.io.Closeable
import java.util.Properties
import javax.sql
class Database(config: Properties) {
private val source = new SQLiteDataSource() with Closeable {
override def close(): Unit = ()
}
source.setUrl(config.getProperty("database.url"))
val layer = DataSourceLayer.fromDataSource(source)
}
object Database {
val ctx = new SqliteZioJdbcContext(Literal)
import ctx._
val app: ZIO[Console with Has[sql.DataSource with Closeable], Exception, Unit] =
for {
_ <- ctx.run(
quote {
liftQuery(List(User(4, "batista", "maxime", 788872465, "myemail@gmail.com")))
.foreach(r => User.schema.insert(r))
}
).onDS
result <- ctx.run(
quote(User.schema.filter(_.name == "batista"))
).onDS
_ <- putStrLn(s"result : $result")
} yield ()
}

@ -0,0 +1,18 @@
package org.tbasket.db.schemas
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"
)
}
}

@ -0,0 +1,16 @@
package org.tbasket.db.schemas
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"
)
}
}

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

@ -0,0 +1,23 @@
package org.tbasket.db.schemas
import org.tbasket.db.Database
case class User(id : Int,
name : String,
forename : String,
passwordHash: Int,
mailAddress : String)
object User {
import Database.ctx._
val schema = quote {
querySchema[User]("user",
_.id -> "id",
_.name -> "name",
_.forename -> "forename",
_.passwordHash -> "password_hash",
_.mailAddress -> "mail_address",
)
}
}

@ -1,39 +0,0 @@
CREATE TABLE user(
id int PRIMARY KEY,
name varchar(30) NOT NULL ,
forename varchar(30) NOT NULL
);
CREATE TABLE team(
id int,
name varchar(30),
club_name varchar(30),
PRIMARY KEY (id)
);
CREATE TABLE tactic_group(
tactic_id int,
group_id int,
PRIMARY KEY(tactic_id, group_id),
FOREIGN KEY (tactic_id) REFERENCES tactic(id),
FOREIGN KEY (group_id) REFERENCES team(id)
);
CREATE TABLE member(
group_id int,
user_id int,
is_manager int NOT NULL,
PRIMARY KEY (group_id, user_id),
FOREIGN KEY (group_id) REFERENCES team(id),
FOREIGN KEY (user_id) REFERENCES user(id)
);
CREATE TABLE tactic(
id int PRIMARY KEY ,
name varchar(30) NOT NULL,
file_path varchar NOT NULL,
owner_id int,
FOREIGN KEY (owner_id) REFERENCES user(id)
);

@ -22,13 +22,10 @@ shadowJar {
}
dependencies {
implementation project(':API')
implementation project(':DB')
implementation 'io.getquill:quill-jdbc_2.13:4.6.0'
implementation 'com.typesafe.play:play-json_2.13:2.10.0-RC7'
testImplementation "io.circe:circe-core_$scalaVersion:0.15.0-M1"
testImplementation "io.circe:circe-parser_$scalaVersion:0.15.0-M1"
implementation 'dev.zio:zio_2.13:2.0.6'
}

@ -2,40 +2,26 @@ package org.tbasket
import org.apache.logging.log4j.LogManager
import org.tbasket.db.Database
import zio._
import java.lang
import java.nio.file.{Files, Path}
import java.util.Properties
import scala.io.StdIn
import scala.util.{Failure, Success}
import scala.util.control.NonFatal
object Main {
object Main extends ZIOAppDefault {
final val LOG = LogManager.getLogger("Core")
def main(args: Array[String]): Unit = {
LOG.info("Starting server")
override def run: ZIO[Main.Environment with ZIOAppArgs with Scope, Any, Any] = {
val config = retrieveConfig
db(config)
api(config)
LOG.info("Server successfully started")
val db = new Database(config)
EndpointSetup.setupEndpoint(config).run
.provide(db.layer)
}
private def db(config: Properties): Unit = new Thread({ () =>
}, "Database").start()
//TODO
private def api(config: Properties): Unit = new Thread({ () =>
val endpoint = EndpointSetup.setupEndpoint(config)
val runtime = Runtime.default
Unsafe.unsafe { implicit u =>
runtime.unsafe.run(endpoint.run).catchSome {
case NonFatal(e) =>
e.printStackTrace()
throw e
}
}
}: Runnable, "API").start()
private def retrieveConfig: Properties = {
val configFile = Path.of("server.properties")

@ -1,10 +0,0 @@
package org.tbasket.data
class User(val id : Int,
val name : String,
val forename: String,
val passwordHash: Int,
val mailAddress : String) {
}

@ -1,21 +1,45 @@
package org.tbasket.handler
import org.tbasket.api.compute.APIRequestHandler
import org.tbasket.db.Database.ctx._
import org.tbasket.db.schemas.User
import org.tbasket.session.UserSessionHandler
import zio.ZIO
import zio.http.model.{Cookie, Headers, Status}
import zio.http.{Request, Response}
import io.getquill._
import org.tbasket.data.User
import zio.{ZEnvironment, ZIO}
object LoginHandler extends APIRequestHandler {
import zio.json._
import zio.json.ast.Json.Str
import zio.json.ast.{Json, JsonCursor}
private val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
import ctx._
private def getAccount(mail: String, passwordHash: Int) = quote {
//query[User].filter(_.mailAddress == mail).filter(_.passwordHash == passwordHash)
class LoginHandler(sessions: UserSessionHandler) extends APIRequestHandler {
private def getUser(json: Json): Either[String, User] = {
(for {
mail <- json.get[Str](JsonCursor.field("mail").isString).map(_.value)
password <- json.get[Str](JsonCursor.field("password").isString).map(_.value)
} yield quote { // TODO use argon2id
User.schema.filter(usr => usr.mailAddress == mail && usr.passwordHash == password.hashCode)
}).flatMap(_.nested.value.toRight("password or email incorrect"))
}
override def post: ZIO[Request, Throwable, Response] = ZIO.serviceWithZIO[Request] { r =>
r.body.asString
.map(_.fromJson[Json].flatMap(getUser) match {
case Left(err) => //if account not found or password mismatches
Response.json(Json.Obj("error" -> Json.Str(err)).toJson)
case Right(user) =>
val session = sessions.bind(user)
override def post(request: Request): Response = {
???
Response(
status = Status.Found,
headers = Headers.location("/") ++ Headers.setCookie(
Cookie(
name = "token",
content = session.token.toString,
maxAge = Some(session.expirationDate)
))
)
}
)
}
}

@ -0,0 +1,9 @@
package org.tbasket.session
import org.tbasket.db.schemas.User
import java.util.UUID
case class UserSession(user: User, token: UUID, expirationDate: Long) {
}

@ -0,0 +1,52 @@
package org.tbasket.session
import org.tbasket.db.schemas.User
import org.tbasket.session.UserSessionHandler.SessionLifespan
import java.time.{Duration, Instant}
import java.util.UUID
import scala.collection.mutable
object UserSessionHandler {
final val SessionLifespan = Duration.ofDays(31)
}
class UserSessionHandler {
private val sessionsUser = mutable.HashMap.empty[User, UserSession]
private val sessionsToken = mutable.HashMap.empty[UUID, UserSession]
def findSession(token: UUID): Option[UserSession] = {
sessionsToken.get(token) match {
case Some(session) if System.currentTimeMillis() > session.expirationDate =>
unbind(token) //session has expired, unbind session
None
case s => s
}
}
private def unbind(token: UUID): Unit = {
sessionsToken.remove(token)
.foreach(s => sessionsUser.remove(s.user))
}
private def unbind(user: User): Unit = {
sessionsUser.remove(user)
.foreach(s => sessionsToken.remove(s.token))
}
def bind(user: User): UserSession = {
if (sessionsUser.contains(user)) {
//if there was a session already bound, then remove it
unbind(user)
}
val uuid = UUID.randomUUID()
val limit = System.currentTimeMillis() + Instant.now().plus(SessionLifespan).toEpochMilli
val session = UserSession(user, uuid, limit)
sessionsUser.put(user, session)
sessionsToken.put(uuid, session)
session
}
}