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

Reviewed-on: #13
pull/14/head
Lucas EVARD 11 months ago
commit de80aecc63

@ -58,6 +58,7 @@ private fun Application.extracted() {
} }
} }
} }
install(ContentNegotiation) { json() } install(ContentNegotiation) { json() }
install(SwaggerUI) { install(SwaggerUI) {
swagger { swagger {

@ -17,4 +17,6 @@ interface BetDataSource {
fun getWonNotifications(username: String): List<BetResultDetail> fun getWonNotifications(username: String): List<BetResultDetail>
fun getHistory(username: String): List<BetResultDetail> fun getHistory(username: String): List<BetResultDetail>
fun getCurrent(username: String): List<BetDetail> fun getCurrent(username: String): List<BetDetail>
fun getMostPopularBet(): Bet?
fun updatePopularityScore(betId: String)
} }

@ -1,8 +1,15 @@
package allin.data.mock package allin.data.mock
import allin.data.BetDataSource import allin.data.BetDataSource
import allin.data.postgres.entities.BetsEntity
import allin.data.postgres.entities.bets
import allin.data.postgres.entities.participations
import allin.model.* import allin.model.*
import allin.model.BetStatus.* import allin.model.BetStatus.*
import org.ktorm.dsl.and
import org.ktorm.dsl.eq
import org.ktorm.dsl.update
import org.ktorm.entity.*
import java.time.ZonedDateTime import java.time.ZonedDateTime
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -199,4 +206,15 @@ class MockBetDataSource(private val mockData: MockDataSource.MockData) : BetData
userParticipation = participation userParticipation = participation
) )
} }
override fun getMostPopularBet() =
mockData.bets.filter { !it.isPrivate && it.status == WAITING }.maxBy { it.popularityscore }
override fun updatePopularityScore(betId: String) {
val bet = mockData.bets.firstOrNull { it.id == betId } ?: return
val participations = mockData.participations.filter { it.betId == betId }
val score = participations.size * participations.size + participations.sumOf { it.stake }
bet.popularityscore = score
}
} }

@ -143,6 +143,24 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource {
} }
} }
override fun getMostPopularBet(): Bet? {
val max=database.bets.filter { (it.isPrivate eq false) and (it.status eq BetStatus.WAITING) }.maxBy { it.popularityscore }
if(max!=null){
return database.bets.filter { (it.popularityscore eq max) and (it.isPrivate eq false) and (it.status eq BetStatus.WAITING) }.map { it.toBet(database) }.first()
}
return null
}
override fun updatePopularityScore(betId: String) {
database.bets.filter { it.id eq betId }.firstOrNull() ?: return
val participations = database.participations.filter { it.betId eq betId }
val score = (participations.count() * participations.count()) + participations.map { it.stake }.sum()
database.update(BetsEntity) {
set(it.popularityscore, score)
where { it.id eq betId }
}
}
override fun addBet(bet: Bet) { override fun addBet(bet: Bet) {
database.bets.add( database.bets.add(
BetEntity { BetEntity {
@ -205,4 +223,5 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource {
} }
} }
} }
} }

@ -48,7 +48,8 @@ class PostgresDataSource : AllInDataSource() {
isprivate boolean, isprivate boolean,
createdby varchar(250), createdby varchar(250),
status varchar(20), status varchar(20),
type varchar(20) type varchar(20),
popularityscore numeric
) )
""".trimIndent() """.trimIndent()
) )

@ -40,15 +40,17 @@ class PostgresUserDataSource(private val database: Database) : UserDataSource {
database.users.removeIf { (it.username eq username) or (it.email eq username) } > 0 database.users.removeIf { (it.username eq username) or (it.email eq username) } > 0
override fun addCoins(username: String, amount: Int) { override fun addCoins(username: String, amount: Int) {
database.users database.update(UsersEntity) {
.find { it.username eq username } set(it.nbCoins, it.nbCoins + amount)
?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins + amount) where { it.username eq username }
}
} }
override fun removeCoins(username: String, amount: Int) { override fun removeCoins(username: String, amount: Int) {
database.users database.update(UsersEntity) {
.find { it.username eq username } set(it.nbCoins, it.nbCoins - amount)
?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins - amount) where { it.username eq username }
}
} }
override fun userExists(username: String) = override fun userExists(username: String) =

@ -5,6 +5,7 @@ import org.ktorm.database.Database
import org.ktorm.dsl.eq import org.ktorm.dsl.eq
import org.ktorm.entity.* import org.ktorm.entity.*
import org.ktorm.schema.* import org.ktorm.schema.*
import org.postgresql.util.ByteConverter.numeric
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -23,6 +24,7 @@ interface BetEntity : Entity<BetEntity> {
var status: BetStatus var status: BetStatus
var type: BetType var type: BetType
var createdBy: String var createdBy: String
var popularityscore: Int
fun toBet(database: Database) = fun toBet(database: Database) =
Bet( Bet(
@ -39,7 +41,8 @@ interface BetEntity : Entity<BetEntity> {
} else { } else {
database.responses.filter { it.betId eq id }.map { it.response } database.responses.filter { it.betId eq id }.map { it.response }
}, },
createdBy = createdBy createdBy = createdBy,
popularityscore = popularityscore,
) )
fun toBetDetail(database: Database, username: String): BetDetail { fun toBetDetail(database: Database, username: String): BetDetail {
@ -60,6 +63,7 @@ interface BetEntity : Entity<BetEntity> {
) )
} }
} }
object BetsEntity : Table<BetEntity>("bet") { object BetsEntity : Table<BetEntity>("bet") {
@ -73,6 +77,7 @@ object BetsEntity : Table<BetEntity>("bet") {
val status = enum<BetStatus>("status").bindTo { it.status } val status = enum<BetStatus>("status").bindTo { it.status }
val type = enum<BetType>("type").bindTo { it.type } val type = enum<BetType>("type").bindTo { it.type }
val createdBy = varchar("createdby").bindTo { it.createdBy } val createdBy = varchar("createdby").bindTo { it.createdBy }
val popularityscore = int("popularityscore").bindTo { it.popularityscore }
} }
val Database.bets get() = this.sequenceOf(BetsEntity) val Database.bets get() = this.sequenceOf(BetsEntity)

@ -19,7 +19,8 @@ data class Bet(
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean, var isPrivate: Boolean,
var response: List<String>, var response: List<String>,
val createdBy: String = "" val createdBy: String = "",
var popularityscore: Int
) )
@Serializable @Serializable

@ -53,7 +53,7 @@ fun Application.betRouter() {
betDataSource.getBetById(id)?.let { betDataSource.getBetById(id)?.let {
call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST) call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST)
} ?: run { } ?: run {
val betWithId = bet.copy(id = id, createdBy = user.first?.username.toString()) val betWithId = bet.copy(id = id, createdBy = user.first?.id.toString())
betDataSource.addBet(betWithId) betDataSource.addBet(betWithId)
call.respond(HttpStatusCode.Created, betWithId) call.respond(HttpStatusCode.Created, betWithId)
} }
@ -90,6 +90,33 @@ fun Application.betRouter() {
} }
} }
authenticate {
get("/bets/popular", {
description = "Allows you to recover the most popular public bets"
request {
headerParameter<JWTPrincipal>("JWT token of the logged user")
}
response {
HttpStatusCode.Accepted to {
description = "The most popular public bet is available"
body<Bet> {
description = "The most popular public bet"
}
}
}
}) {
hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { _, _ ->
val bet = betDataSource.getMostPopularBet()
if (bet != null) {
call.respond(HttpStatusCode.Accepted, bet)
}
call.respond(HttpStatusCode.NotFound,"Aucun bet n'a pu être récupérer")
}
}
}
}
get("/bets/get/{id}", { get("/bets/get/{id}", {
description = "Retrieves a specific bet" description = "Retrieves a specific bet"
request { request {

@ -22,6 +22,7 @@ fun Application.participationRouter() {
val userDataSource = this.dataSource.userDataSource val userDataSource = this.dataSource.userDataSource
val participationDataSource = this.dataSource.participationDataSource val participationDataSource = this.dataSource.participationDataSource
val betDataSource = this.dataSource.betDataSource
routing { routing {
authenticate { authenticate {
@ -47,6 +48,11 @@ fun Application.participationRouter() {
hasToken { principal -> hasToken { principal ->
val participation = call.receive<ParticipationRequest>() val participation = call.receive<ParticipationRequest>()
verifyUserFromToken(userDataSource, principal) { user, _ -> verifyUserFromToken(userDataSource, principal) { user, _ ->
if(betDataSource.getBetById(participation.betId)== null){
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
}
if (user.nbCoins >= participation.stake) { if (user.nbCoins >= participation.stake) {
participationDataSource.addParticipation( participationDataSource.addParticipation(
Participation( Participation(
@ -59,6 +65,7 @@ fun Application.participationRouter() {
) )
userDataSource.removeCoins(username = user.username, amount = participation.stake) userDataSource.removeCoins(username = user.username, amount = participation.stake)
betDataSource.updatePopularityScore(participation.betId)
call.respond(HttpStatusCode.Created) call.respond(HttpStatusCode.Created)
} else { } else {
call.respond(HttpStatusCode.Forbidden, ApiMessage.NOT_ENOUGH_COINS) call.respond(HttpStatusCode.Forbidden, ApiMessage.NOT_ENOUGH_COINS)

@ -56,24 +56,25 @@ fun Application.userRouter() {
if (RegexCheckerUser.isEmailInvalid(tempUser.email)) { if (RegexCheckerUser.isEmailInvalid(tempUser.email)) {
call.respond(HttpStatusCode.Forbidden, ApiMessage.INVALID_MAIL) call.respond(HttpStatusCode.Forbidden, ApiMessage.INVALID_MAIL)
} }
if (userDataSource.userExists(tempUser.username)) { else if (userDataSource.userExists(tempUser.username)) {
call.respond(HttpStatusCode.Conflict, ApiMessage.USER_ALREADY_EXISTS) call.respond(HttpStatusCode.Conflict, ApiMessage.USER_ALREADY_EXISTS)
} }
if(userDataSource.emailExists(tempUser.email)){ else if (userDataSource.emailExists(tempUser.email)) {
call.respond(HttpStatusCode.Conflict, ApiMessage.MAIL_ALREADY_EXISTS) call.respond(HttpStatusCode.Conflict, ApiMessage.MAIL_ALREADY_EXISTS)
} else {
val user = User(
id = UUID.randomUUID().toString(),
username = tempUser.username,
email = tempUser.email,
password = tempUser.password,
nbCoins = DEFAULT_COINS,
token = null
)
CryptManagerUser.passwordCrypt(user)
user.token = tokenManagerUser.generateOrReplaceJWTToken(user)
userDataSource.addUser(user)
call.respond(HttpStatusCode.Created, user)
} }
val user = User(
id = UUID.randomUUID().toString(),
username = tempUser.username,
email = tempUser.email,
password = tempUser.password,
nbCoins = DEFAULT_COINS,
token = null
)
CryptManagerUser.passwordCrypt(user)
user.token = tokenManagerUser.generateOrReplaceJWTToken(user)
userDataSource.addUser(user)
call.respond(HttpStatusCode.Created, user)
} }
post("/users/login", { post("/users/login", {

Loading…
Cancel
Save