working on backend
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
ebcbc8bab7
commit
27b808abe3
@ -1,8 +1,10 @@
|
||||
build
|
||||
.gradle
|
||||
.idea
|
||||
*.sqlite
|
||||
*/.gradle/
|
||||
log
|
||||
|
||||
|
||||
*.sqlite
|
||||
|
||||
.$*
|
||||
|
@ -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 @@
|
||||
path=database.sqlite
|
@ -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)
|
||||
);
|
@ -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
|
||||
}
|
||||
|
||||
}
|
Reference in new issue