Merge pull request 'bet_confirmation' (#10) from bet_confirmation into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #10
pull/12/head
Arthur VALIN 1 year ago
commit 464bdfeee1

@ -5,6 +5,7 @@ import allin.data.mock.MockDataSource
import allin.data.postgres.PostgresDataSource
import allin.routing.*
import allin.utils.TokenManager
import allin.utils.TokenManager.Companion.Claims.USERNAME
import allin.utils.kronJob
import com.typesafe.config.ConfigFactory
import io.ktor.serialization.kotlinx.json.*
@ -48,7 +49,7 @@ private fun Application.extracted() {
verifier(tokenManager.verifyJWTToken())
realm = config.property("realm").getString()
validate { jwtCredential ->
if (jwtCredential.payload.getClaim("username").asString().isNotEmpty())
if (jwtCredential.payload.getClaim(USERNAME).asString().isNotEmpty())
JWTPrincipal(jwtCredential.payload)
else null
}

@ -1,12 +1,7 @@
package allin.data
abstract class AllInDataSource {
abstract val userDataSource: UserDataSource
abstract val betDataSource: BetDataSource
abstract val participationDataSource: ParticipationDataSource
}

@ -1,6 +1,8 @@
package allin.data
import allin.model.Bet
import allin.model.BetDetail
import allin.model.BetResultDetail
import allin.model.UpdatedBetData
import java.time.ZonedDateTime
@ -12,4 +14,9 @@ interface BetDataSource {
fun removeBet(id: String): Boolean
fun updateBet(data: UpdatedBetData): Boolean
fun updateBetStatuses(date: ZonedDateTime)
fun getToConfirm(username: String): List<Bet>
fun confirmBet(betId: String, result: String)
fun getWonNotifications(username: String): List<BetResultDetail>
fun getHistory(username: String): List<BetResultDetail>
fun getCurrent(username: String): List<BetDetail>
}

@ -3,16 +3,8 @@ package allin.data
import allin.model.Participation
interface ParticipationDataSource {
fun addParticipation(participation: Participation)
fun getParticipationFromBetId(betid: String): List<Participation>
fun getParticipationFromUserId(username: String, betid: String): List<Participation>
fun deleteParticipation(id: String): Boolean
}

@ -6,18 +6,10 @@ import allin.model.User
interface UserDataSource {
fun getUserByUsername(username: String): Pair<UserDTO?, String?>
fun addUser(user: User)
fun deleteUser(username: String): Boolean
fun addCoins(username: String, amount: Int)
fun removeCoins(username: String, amount: Int)
fun userExists(username: String, email: String): Boolean
fun canHaveDailyGift(username: String): Boolean
}

@ -1,12 +1,16 @@
package allin.data.mock
import allin.data.BetDataSource
import allin.model.Bet
import allin.model.BetStatus
import allin.model.UpdatedBetData
import allin.model.*
import allin.model.BetStatus.*
import java.time.ZonedDateTime
class MockBetDataSource : BetDataSource {
class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource {
private val bets = mockData.bets
private val results = mockData.results
private val participations = mockData.participations
private val resultNotifications = mockData.resultNotifications
override fun getAllBets(): List<Bet> = bets
override fun getBetById(id: String): Bet? =
bets.find { it.id == id }
@ -31,14 +35,91 @@ class MockBetDataSource : BetDataSource {
bets.forEachIndexed { idx, bet ->
if (date >= bet.endRegistration) {
if (date >= bet.endBet) {
bets[idx] = bet.copy(status = BetStatus.WAITING)
bets[idx] = bet.copy(status = WAITING)
} else {
bets[idx] = bet.copy(status = BetStatus.CLOSING)
bets[idx] = bet.copy(status = CLOSING)
}
}
}
}
private val bets by lazy { mutableListOf<Bet>() }
override fun getToConfirm(username: String): List<Bet> =
bets.filter { it.createdBy == username && it.status == CLOSING }
override fun confirmBet(betId: String, result: String) {
results.add(
BetResult(
betId = betId,
result = result
)
)
bets.replaceAll {
if (it.id == betId) {
it.copy(status = FINISHED)
} else it
}
participations.filter { it.betId == betId && it.answer == result }
.forEach {
resultNotifications.add(Pair(betId, it.username))
}
}
override fun getWonNotifications(username: String): List<BetResultDetail> {
return bets.map { bet ->
val notification = resultNotifications.find { it.first == bet.id } ?: return@map null
val result = results.find { it.betId == bet.id } ?: return@map null
val participation = participations.find { it.username == username && it.betId == bet.id }
?: return@map null
if (participation.answer == result.result) {
resultNotifications.remove(notification)
BetResultDetail(
betResult = result,
bet = bet,
participation = participation,
amount = participation.stake,
won = true
)
} else null
}.mapNotNull { it }
}
override fun getHistory(username: String): List<BetResultDetail> {
return bets.map { bet ->
val result = results.find { it.betId == bet.id } ?: return@map null
val participation = participations.find { it.username == username && it.betId == bet.id }
?: return@map null
BetResultDetail(
betResult = result,
bet = bet,
participation = participation,
amount = participation.stake,
won = participation.answer == result.result
)
}.mapNotNull { it }
}
override fun getCurrent(username: String): List<BetDetail> {
return bets.map { bet ->
when (bet.status) {
CANCELLED, FINISHED -> return@map null
else -> {
val participation = participations.find { it.username == username && it.betId == bet.id }
?: return@map null
val participations = participations.filter { it.betId == bet.id }
}
BetDetail(
bet = bet,
answers = getBetAnswerDetail(bet, participations),
participations = participations,
userParticipation = participation
)
}
}
}.mapNotNull { it }
}
}

@ -4,9 +4,26 @@ import allin.data.AllInDataSource
import allin.data.BetDataSource
import allin.data.ParticipationDataSource
import allin.data.UserDataSource
import allin.model.Bet
import allin.model.BetResult
import allin.model.Participation
import allin.model.User
import java.time.ZonedDateTime
class MockDataSource : AllInDataSource() {
override val userDataSource: UserDataSource = MockUserDataSource()
override val betDataSource: BetDataSource = MockBetDataSource()
override val participationDataSource: ParticipationDataSource = MockParticipationDataSource()
class MockData {
val bets by lazy { mutableListOf<Bet>() }
val results by lazy { mutableListOf<BetResult>() }
val resultNotifications by lazy { mutableListOf<Pair<String, String>>() }
val users by lazy { mutableListOf<User>() }
val lastGifts by lazy { mutableMapOf<String, ZonedDateTime>() }
val participations by lazy { mutableListOf<Participation>() }
}
private val mockData by lazy { MockData() }
override val userDataSource: UserDataSource = MockUserDataSource(mockData)
override val betDataSource: BetDataSource = MockBetDataSource(mockData)
override val participationDataSource: ParticipationDataSource = MockParticipationDataSource(mockData)
}

@ -3,7 +3,9 @@ package allin.data.mock
import allin.data.ParticipationDataSource
import allin.model.Participation
class MockParticipationDataSource : ParticipationDataSource {
class MockParticipationDataSource(mockData: MockDataSource.MockData) : ParticipationDataSource {
private val participations = mockData.participations
override fun addParticipation(participation: Participation) {
participations += participations
}
@ -16,7 +18,4 @@ class MockParticipationDataSource : ParticipationDataSource {
override fun deleteParticipation(id: String): Boolean =
participations.removeIf { it.id == id }
private val participations by lazy { mutableListOf<Participation>() }
}

@ -5,7 +5,11 @@ import allin.dto.UserDTO
import allin.model.User
import java.time.ZonedDateTime
class MockUserDataSource : UserDataSource {
class MockUserDataSource(mockData: MockDataSource.MockData) : UserDataSource {
private val users = mockData.users
private val lastGifts = mockData.lastGifts
override fun getUserByUsername(username: String): Pair<UserDTO?, String?> =
users.find { it.username == username }?.let {
Pair(
@ -49,12 +53,4 @@ class MockUserDataSource : UserDataSource {
lastGifts[username] = ZonedDateTime.now()
return value
}
private val users by lazy {
mutableListOf<User>()
}
private val lastGifts by lazy {
mutableMapOf<String, ZonedDateTime>()
}
}

@ -1,15 +1,9 @@
package allin.data.postgres
import allin.data.BetDataSource
import allin.entities.BetsEntity
import allin.entities.NO_VALUE
import allin.entities.ResponsesEntity
import allin.entities.ResponsesEntity.response
import allin.entities.YES_VALUE
import allin.model.Bet
import allin.model.BetStatus
import allin.model.BetType
import allin.model.UpdatedBetData
import allin.data.postgres.entities.*
import allin.data.postgres.entities.ResponsesEntity.response
import allin.model.*
import org.ktorm.database.Database
import org.ktorm.dsl.*
import java.time.ZoneId
@ -43,7 +37,29 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource {
}
)
private fun QueryRowSet.toParticipation() =
Participation(
id = this[ParticipationsEntity.id]?.toString() ?: "",
betId = this[ParticipationsEntity.betId]?.toString() ?: "",
username = this[ParticipationsEntity.username] ?: "",
answer = this[ParticipationsEntity.answer] ?: "",
stake = this[ParticipationsEntity.stake] ?: 0
)
private fun QueryRowSet.toBetResultDetail() =
BetResultDetail(
betResult = BetResult(
betId = this[BetResultsEntity.betId]?.toString() ?: "",
result = this[BetResultsEntity.result] ?: ""
),
bet = this.toBet(),
participation = this.toParticipation(),
amount = this[ParticipationsEntity.stake] ?: 0,
won = this[ParticipationsEntity.answer] == this[BetResultsEntity.result]
)
private fun Query.mapToBet() = this.map { it.toBet() }
private fun Query.mapToBetResultDetail() = this.map { it.toBetResultDetail() }
override fun getAllBets(): List<Bet> =
database.from(BetsEntity).select().mapToBet()
@ -61,6 +77,94 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource {
.mapToBet()
}
override fun getToConfirm(username: String): List<Bet> {
return database.from(BetsEntity)
.select()
.where {
(BetsEntity.createdBy eq username) and
(BetsEntity.status eq BetStatus.CLOSING)
}.mapToBet()
}
override fun confirmBet(betId: String, result: String) {
database.insert(BetResultsEntity) {
set(it.betId, betId)
set(it.result, result)
}
database.update(BetsEntity) {
where { BetsEntity.id eq UUID.fromString(betId) }
set(BetsEntity.status, BetStatus.FINISHED)
}
database.from(ParticipationsEntity)
.select()
.where {
(ParticipationsEntity.betId eq UUID.fromString(betId)) and
(ParticipationsEntity.answer eq result)
}
.forEach { participation ->
database.insert(BetResultNotificationsEntity) {
set(it.betId, betId)
set(it.username, participation[ParticipationsEntity.username])
}
}
}
override fun getWonNotifications(username: String): List<BetResultDetail> {
return database.from(BetsEntity)
.innerJoin(ParticipationsEntity, on = BetsEntity.id eq ParticipationsEntity.betId)
.innerJoin(BetResultsEntity, on = BetsEntity.id eq BetResultsEntity.betId)
.innerJoin(BetResultNotificationsEntity, on = BetsEntity.id eq BetResultNotificationsEntity.betId)
.select()
.where {
(BetResultsEntity.result eq ParticipationsEntity.answer) and
(ParticipationsEntity.username eq username)
}.let {
it.forEach { row ->
row[BetsEntity.id]?.let { betId ->
database.delete(BetResultNotificationsEntity) {
(it.betId eq betId) and (it.username eq username)
}
}
}
it
}.mapToBetResultDetail()
}
override fun getHistory(username: String): List<BetResultDetail> {
return database.from(BetsEntity)
.innerJoin(ParticipationsEntity, on = BetsEntity.id eq ParticipationsEntity.betId)
.innerJoin(BetResultsEntity, on = BetsEntity.id eq BetResultsEntity.betId)
.select()
.where { ParticipationsEntity.username eq username }.mapToBetResultDetail()
}
override fun getCurrent(username: String): List<BetDetail> {
return database.from(BetsEntity)
.innerJoin(ParticipationsEntity, on = BetsEntity.id eq ParticipationsEntity.betId)
.select()
.where {
(BetsEntity.status notEq BetStatus.FINISHED) and
(BetsEntity.status notEq BetStatus.CANCELLED) and
(ParticipationsEntity.username eq username)
}.map {
val participations = it[BetsEntity.id]?.let { betId ->
database.from(ParticipationsEntity)
.select().where { ParticipationsEntity.betId eq betId }.map { it.toParticipation() }
} ?: emptyList()
val bet = it.toBet()
BetDetail(
bet = bet,
answers = getBetAnswerDetail(bet, participations),
participations = participations,
userParticipation = it.toParticipation()
)
}
}
override fun addBet(bet: Bet) {
database.insert(BetsEntity) {
set(it.id, UUID.fromString(bet.id))

@ -32,17 +32,12 @@ class PostgresDataSource : AllInDataSource() {
coins double precision,
email VARCHAR(255),
lastgift timestamp
)""".trimIndent()
)
""".trimIndent()
)
database.Execute(
"""
CREATE TYPE betstatus AS ENUM
('InProgress', 'Waiting', 'Closing', 'Finished', 'Cancelled');
CREATE TYPE bettype AS ENUM
('Match', 'Binary', 'Custom');
CREATE TABLE IF not exists bet (
id uuid PRIMARY KEY,
theme VARCHAR(255),
@ -53,7 +48,22 @@ class PostgresDataSource : AllInDataSource() {
createdby varchar(250),
status varchar(20),
type varchar(20)
)""".trimIndent()
)
""".trimIndent()
)
database.Execute(
"""
CREATE TABLE IF NOT EXISTS betresult (
betid uuid PRIMARY KEY REFERENCES bet,
result varchar(250)
)
CREATE TABLE IF NOT EXISTS betresultnotification (
betid uuid,
username varchar(250),
CONSTRAINT pk_id_username PRIMARY KEY (betid, username)
)
""".trimIndent()
)
database.Execute(
@ -64,7 +74,8 @@ class PostgresDataSource : AllInDataSource() {
username varchar(250),
answer varchar(250),
stake int
)""".trimIndent()
)
""".trimIndent()
)
database.Execute(
@ -72,8 +83,9 @@ class PostgresDataSource : AllInDataSource() {
CREATE TABLE IF NOT EXISTS response (
id UUID,
response VARCHAR(250),
CONSTRAINT pk_response_id PRIMARY KEY (id,response)
)""".trimIndent()
CONSTRAINT pk_response_id PRIMARY KEY (id, response)
)
""".trimIndent()
)
}

@ -1,7 +1,7 @@
package allin.data.postgres
import allin.data.ParticipationDataSource
import allin.entities.ParticipationsEntity
import allin.data.postgres.entities.ParticipationsEntity
import allin.model.Participation
import org.ktorm.database.Database
import org.ktorm.dsl.*
@ -30,28 +30,23 @@ class PostgresParticipationDataSource(private val database: Database) : Particip
}
}
override fun getParticipationFromBetId(betid: String): List<Participation> {
return database.from(ParticipationsEntity)
override fun getParticipationFromBetId(betid: String): List<Participation> =
database.from(ParticipationsEntity)
.select()
.where { ParticipationsEntity.betId eq UUID.fromString(betid) }
.mapToParticipation()
}
override fun getParticipationFromUserId(username: String, betid: String): List<Participation> {
return database.from(ParticipationsEntity)
override fun getParticipationFromUserId(username: String, betid: String): List<Participation> =
database.from(ParticipationsEntity)
.select()
.where { (ParticipationsEntity.betId eq UUID.fromString(betid)) and (ParticipationsEntity.username eq username) }
.mapToParticipation()
}
fun getParticipationEntity(): List<Participation> {
return database.from(ParticipationsEntity).select().mapToParticipation()
}
fun getParticipationEntity(): List<Participation> =
database.from(ParticipationsEntity).select().mapToParticipation()
override fun deleteParticipation(id: String): Boolean {
return database.delete(ParticipationsEntity) {
override fun deleteParticipation(id: String): Boolean =
database.delete(ParticipationsEntity) {
it.id eq UUID.fromString(id)
} > 0
}
}

@ -1,8 +1,8 @@
package allin.data.postgres
import allin.data.UserDataSource
import allin.data.postgres.entities.UsersEntity
import allin.dto.UserDTO
import allin.entities.UsersEntity
import allin.model.User
import allin.utils.ExecuteWithResult
import org.ktorm.database.Database

@ -1,10 +1,9 @@
package allin.entities
package allin.data.postgres.entities
import allin.model.BetStatus
import allin.model.BetType
import org.ktorm.entity.Entity
import org.ktorm.schema.*
import org.ktorm.support.postgresql.pgEnum
import java.time.ZonedDateTime
@ -21,12 +20,12 @@ interface BetEntity : Entity<BetEntity> {
object BetsEntity : Table<BetEntity>("bet") {
val id = uuid("id").primaryKey()
val theme = varchar("theme")
val sentenceBet = varchar("sentencebet")
val theme = varchar("theme").bindTo { it.theme }
val sentenceBet = varchar("sentencebet").bindTo { it.sentenceBet }
val endRegistration = timestamp("endregistration")
val endBet = timestamp("endbet")
val isPrivate = boolean("isprivate")
val status = pgEnum<BetStatus>("status").bindTo { it.status }
val type = pgEnum<BetType>("type").bindTo { it.type }
val createdBy = varchar("createdby")
val isPrivate = boolean("isprivate").bindTo { it.isPrivate }
val status = enum<BetStatus>("status").bindTo { it.status }
val type = enum<BetType>("type").bindTo { it.type }
val createdBy = varchar("createdby").bindTo { it.createdBy }
}

@ -0,0 +1,28 @@
package allin.data.postgres.entities
import org.ktorm.entity.Entity
import org.ktorm.schema.Table
import org.ktorm.schema.uuid
import org.ktorm.schema.varchar
import java.util.*
interface BetResultEntity : Entity<BetResultEntity> {
val bet: BetEntity
val result: String
}
object BetResultsEntity : Table<BetResultEntity>("betresult") {
val betId = uuid("betid").primaryKey().references(BetsEntity) { it.bet }
val result = varchar("result").bindTo { it.result }
}
interface BetResultNotificationEntity : Entity<BetResultNotificationEntity> {
val betId: UUID
val username: String
}
object BetResultNotificationsEntity : Table<BetResultNotificationEntity>("betresult") {
val betId = uuid("betid").primaryKey()
val username = varchar("username").primaryKey()
}

@ -1,4 +1,4 @@
package allin.entities
package allin.data.postgres.entities
import org.ktorm.entity.Entity
import org.ktorm.schema.Table
@ -8,16 +8,16 @@ import org.ktorm.schema.varchar
interface ParticipationEntity : Entity<ParticipationEntity> {
val id: String
val betId: String
val bet: BetEntity
val username: String
val answer: String
val stake: Int
}
object ParticipationsEntity : Table<BetEntity>("participation") {
object ParticipationsEntity : Table<ParticipationEntity>("participation") {
val id = uuid("id").primaryKey()
val betId = uuid("bet")
val betId = uuid("bet").references(BetsEntity) { it.bet }
val username = varchar("username")
val answer = varchar("answer")
val stake = int("stake")

@ -1,4 +1,4 @@
package allin.entities
package allin.data.postgres.entities
import org.ktorm.entity.Entity
import org.ktorm.schema.Table

@ -1,4 +1,4 @@
package allin.entities
package allin.data.postgres.entities
import org.ktorm.entity.Entity
import org.ktorm.schema.*
@ -12,10 +12,10 @@ interface UserEntity : Entity<UserEntity> {
object UsersEntity : Table<UserEntity>("utilisateur") {
val id = uuid("id").primaryKey()
val username = varchar("username")
val password = varchar("password")
val nbCoins = int("coins")
val email = varchar("email")
val username = varchar("username").bindTo { it.username }
val password = varchar("password").bindTo { it.password }
val nbCoins = int("coins").bindTo { it.nbCoins }
val email = varchar("email").bindTo { it.email }
val lastGift = timestamp("lastgift")
}

@ -1,4 +1,12 @@
package allin.dto
import kotlinx.serialization.Serializable
@Serializable
data class UserDTO(val id: String, val username: String, val email: String, val nbCoins: Int, var token:String?)
data class UserDTO(
val id: String,
val username: String,
val email: String,
val nbCoins: Int,
var token: String?
)

@ -3,6 +3,7 @@ package allin.ext
import allin.data.UserDataSource
import allin.dto.UserDTO
import allin.model.ApiMessage
import allin.utils.TokenManager.Companion.Claims.USERNAME
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
@ -10,18 +11,16 @@ import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
import io.ktor.util.pipeline.*
suspend fun PipelineContext<*, ApplicationCall>.hasToken(content: suspend (principal: JWTPrincipal) -> Unit) =
call.principal<JWTPrincipal>()?.let { content(it) } ?: call.respond(HttpStatusCode.Unauthorized)
suspend fun PipelineContext<*, ApplicationCall>.verifyUserFromToken(
userDataSource: UserDataSource,
principal: JWTPrincipal,
content: suspend (user: UserDTO, password: String) -> Unit
) {
val username = principal.payload.getClaim("username").asString()
val username = principal.payload.getClaim(USERNAME).asString()
val userPassword = userDataSource.getUserByUsername(username)
userPassword.first?.let { content(it, userPassword.second ?: "") }
?: call.respond(HttpStatusCode.NotFound, ApiMessage.TokenUserNotFound)
?: call.respond(HttpStatusCode.NotFound, ApiMessage.TOKEN_USER_NOT_FOUND)
}

@ -1,14 +1,15 @@
package allin.model
object ApiMessage {
const val Welcome = "Welcome on AllIn's API !"
const val TokenUserNotFound = "User not found with the valid token !"
const val UserNotFound = "User not found."
const val BetNotFound = "Bet not found."
const val BetAlreadyExist = "Bet already exists."
const val IncorrectLoginPassword = "Login and/or password incorrect."
const val UserAlreadyExist = "Mail and/or username already exists."
const val InvalidMail = "Invalid mail."
const val ParticipationNotFound = "Participation not found."
const val NotEnoughCoins = "Not enough coins."
const val WELCOME = "Welcome on AllIn's API !"
const val TOKEN_USER_NOT_FOUND = "User not found with the valid token !"
const val USER_NOT_FOUND = "User not found."
const val BET_NOT_FOUND = "Bet not found."
const val BET_ALREADY_EXIST = "Bet already exists."
const val INCORRECT_LOGIN_PASSWORD = "Login and/or password incorrect."
const val USER_ALREADY_EXISTS = "Mail and/or username already exists."
const val INVALID_MAIL = "Invalid mail."
const val PARTICIPATION_NOT_FOUND = "Participation not found."
const val NOT_ENOUGH_COINS = "Not enough coins."
const val NO_GIFT = "Can't get daily gift."
}

@ -0,0 +1,9 @@
package allin.model
import kotlinx.serialization.Serializable
@Serializable
data class BetResult(
val betId: String,
val result: String
)

@ -0,0 +1,12 @@
package allin.model
import kotlinx.serialization.Serializable
@Serializable
data class BetResultDetail(
val betResult: BetResult,
val bet: Bet,
val participation: Participation,
val amount: Int,
val won: Boolean
)

@ -1,5 +1,6 @@
package allin.routing
import allin.model.ApiMessage
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
@ -8,7 +9,7 @@ import io.ktor.server.routing.*
fun Application.BasicRouting() {
routing {
get("/") {
call.respond("Bienvenue sur l'API de AlLin!")
call.respond(ApiMessage.WELCOME)
}
}
}

@ -3,6 +3,7 @@ package allin.routing
import allin.dataSource
import allin.ext.hasToken
import allin.ext.verifyUserFromToken
import allin.model.ApiMessage
import allin.model.BetDetail
import allin.model.getBetAnswerDetail
import io.ktor.http.*
@ -36,7 +37,7 @@ fun Application.BetDetailRouter() {
)
)
} else {
call.respond(HttpStatusCode.NotFound, "Bet not found")
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
}
}
}

@ -3,9 +3,7 @@ package allin.routing
import allin.dataSource
import allin.ext.hasToken
import allin.ext.verifyUserFromToken
import allin.model.ApiMessage
import allin.model.Bet
import allin.model.UpdatedBetData
import allin.model.*
import allin.utils.AppConfig
import io.ktor.http.*
import io.ktor.server.application.*
@ -32,7 +30,7 @@ fun Application.BetRouter() {
val id = UUID.randomUUID().toString()
val username = tokenManagerBet.getUsernameFromToken(principal)
betDataSource.getBetById(id)?.let {
call.respond(HttpStatusCode.Conflict, ApiMessage.BetAlreadyExist)
call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST)
} ?: run {
val betWithId = bet.copy(id = id, createdBy = username)
betDataSource.addBet(betWithId)
@ -44,12 +42,13 @@ fun Application.BetRouter() {
}
}
route("/bets/gets") {
get {
// if(bets.size>0)
call.respond(HttpStatusCode.Accepted, betDataSource.getAllBets())
// else call.respond(HttpStatusCode.NoContent)
authenticate {
get("/bets/gets") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
call.respond(HttpStatusCode.Accepted, betDataSource.getAllBets())
}
}
}
}
@ -58,7 +57,7 @@ fun Application.BetRouter() {
val id = call.parameters["id"] ?: ""
betDataSource.getBetById(id)?.let { bet ->
call.respond(HttpStatusCode.Accepted, bet)
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
}
}
@ -68,7 +67,7 @@ fun Application.BetRouter() {
if (betDataSource.removeBet(id)) {
call.respond(HttpStatusCode.Accepted)
} else {
call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
}
}
@ -79,7 +78,46 @@ fun Application.BetRouter() {
if (betDataSource.updateBet(updatedBetData)) {
call.respond(HttpStatusCode.Accepted)
} else {
call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
}
}
}
authenticate {
get("/bets/toConfirm") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
val response = betDataSource.getToConfirm(user.username).map {
val participations = participationDataSource.getParticipationFromBetId(it.id)
BetDetail(
it,
getBetAnswerDetail(it, participations),
participations.toList(),
participationDataSource.getParticipationFromUserId(user.username, it.id).lastOrNull()
)
}
call.respond(HttpStatusCode.Accepted, response)
}
}
}
}
authenticate {
get("/bets/getWon") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
call.respond(HttpStatusCode.Accepted, betDataSource.getWonNotifications(user.username))
}
}
}
}
authenticate {
get("/bets/history") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
call.respond(HttpStatusCode.Accepted, betDataSource.getHistory(user.username))
}
}
}
}
@ -88,17 +126,30 @@ fun Application.BetRouter() {
get("/bets/current") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
val currentBets = betDataSource.getBetsNotFinished()
.filter { bet ->
val userParticipation =
participationDataSource.getParticipationFromUserId(user.username, bet.id)
userParticipation.isNotEmpty()
}
call.respond(HttpStatusCode.OK, currentBets)
call.respond(HttpStatusCode.Accepted, betDataSource.getCurrent(user.username))
}
}
}
}
authenticate {
post("/bets/confirm/{id}") {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { user, _ ->
val betId = call.parameters["id"] ?: ""
val result = call.receive<String>()
if (betDataSource.getBetById(betId)?.createdBy == user.username) {
betDataSource.confirmBet(betId, result)
call.respond(HttpStatusCode.OK)
} else {
call.respond(HttpStatusCode.Unauthorized)
}
}
}
}
}
}
}

@ -41,7 +41,7 @@ fun Application.ParticipationRouter() {
call.respond(HttpStatusCode.Created)
} else {
call.respond(HttpStatusCode.Forbidden, ApiMessage.NotEnoughCoins)
call.respond(HttpStatusCode.Forbidden, ApiMessage.NOT_ENOUGH_COINS)
}
}
}
@ -52,7 +52,7 @@ fun Application.ParticipationRouter() {
if (participationDataSource.deleteParticipation(participationId)) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, ApiMessage.ParticipationNotFound)
call.respond(HttpStatusCode.NotFound, ApiMessage.PARTICIPATION_NOT_FOUND)
}
}
}

@ -28,10 +28,10 @@ fun Application.UserRouter() {
post {
val tempUser = call.receive<UserRequest>()
if (RegexCheckerUser.isEmailInvalid(tempUser.email)) {
call.respond(HttpStatusCode.Forbidden, ApiMessage.InvalidMail)
call.respond(HttpStatusCode.Forbidden, ApiMessage.INVALID_MAIL)
}
if (userDataSource.userExists(tempUser.username, tempUser.email)) {
call.respond(HttpStatusCode.Conflict, ApiMessage.UserAlreadyExist)
call.respond(HttpStatusCode.Conflict, ApiMessage.USER_ALREADY_EXISTS)
}
val user = User(
@ -57,9 +57,9 @@ fun Application.UserRouter() {
user.first?.let { userDtoWithToken ->
userDtoWithToken.token = tokenManagerUser.generateOrReplaceJWTToken(userDtoWithToken)
call.respond(HttpStatusCode.OK, userDtoWithToken)
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.UserNotFound)
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.USER_NOT_FOUND)
} else {
call.respond(HttpStatusCode.NotFound, ApiMessage.IncorrectLoginPassword)
call.respond(HttpStatusCode.NotFound, ApiMessage.INCORRECT_LOGIN_PASSWORD)
}
}
}
@ -75,7 +75,7 @@ fun Application.UserRouter() {
}
call.respond(HttpStatusCode.Accepted, password)
} else {
call.respond(HttpStatusCode.NotFound, "Login and/or password incorrect.")
call.respond(HttpStatusCode.NotFound, ApiMessage.INCORRECT_LOGIN_PASSWORD)
}
}
@ -96,7 +96,7 @@ fun Application.UserRouter() {
val dailyGift = getDailyGift()
userDataSource.addCoins(userDto.username, dailyGift)
call.respond(HttpStatusCode.OK, dailyGift)
} else call.respond(HttpStatusCode.MethodNotAllowed, "Can't get daily gift.")
} else call.respond(HttpStatusCode.MethodNotAllowed, ApiMessage.NO_GIFT)
}
}
}

@ -1,13 +1,10 @@
package allin.utils
class RegexChecker {
private val emailRegex="^[A-Za-z0-9+_.-]+@(.+)$"
private val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
fun isEmailInvalid(email: String): Boolean {
val emailRegex = Regex(emailRegex)
return !emailRegex.matches(email)
}
}

@ -2,26 +2,26 @@ package allin.utils
import allin.dto.UserDTO
import allin.model.User
import allin.utils.TokenManager.Companion.Claims.USERNAME
import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.DecodedJWT
import io.ktor.server.auth.jwt.*
import io.ktor.server.config.*
import java.util.*
class TokenManager private constructor(val config: HoconApplicationConfig) {
class TokenManager private constructor(config: HoconApplicationConfig) {
val audience = config.property("audience").getString()
val secret = config.property("secret").getString()
val issuer = config.property("issuer").getString()
fun generateJWTToken(user: User): String {
private val audience = config.property("audience").getString()
private val secret = config.property("secret").getString()
private val issuer = config.property("issuer").getString()
private fun generateJWTToken(user: User): String {
val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde
return JWT.create()
.withAudience(audience)
.withIssuer(issuer)
.withClaim("username", user.username)
.withClaim(USERNAME, user.username)
.withExpiresAt(Date(expirationDate))
.sign(Algorithm.HMAC256(secret))
}
@ -52,30 +52,34 @@ class TokenManager private constructor(val config: HoconApplicationConfig) {
}
}
fun generateJWTToken(user: UserDTO): String {
private fun generateJWTToken(user: UserDTO): String {
val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde
return JWT.create()
.withAudience(audience)
.withIssuer(issuer)
.withClaim("username", user.username)
.withClaim(USERNAME, user.username)
.withExpiresAt(Date(expirationDate))
.sign(Algorithm.HMAC256(secret))
}
fun isTokenExpired(token: String): Boolean {
private fun isTokenExpired(token: String): Boolean {
val expirationTime = JWT.decode(token).expiresAt.time
return System.currentTimeMillis() > expirationTime
}
fun getUserToken(user: User): String? = user.token
fun getUserToken(user: UserDTO): String? = user.token
private fun getUserToken(user: User): String? = user.token
private fun getUserToken(user: UserDTO): String? = user.token
fun getUsernameFromToken(principal: JWTPrincipal): String {
return principal.payload.getClaim("username").asString()
return principal.payload.getClaim(USERNAME).asString()
}
companion object {
object Claims {
const val USERNAME = "username"
}
private var instance: TokenManager? = null
fun getInstance(config: HoconApplicationConfig): TokenManager {
return instance ?: synchronized(this) {

@ -1,4 +1,4 @@
secret="secret"
issuer="http://0.0.0.0:8080/"
audience="http://0.0.0.0:8080/"
realm="Access to main page"
realm="allin"
Loading…
Cancel
Save