diff --git a/Sources/src/main/kotlin/allin/Application.kt b/Sources/src/main/kotlin/allin/Application.kt index 222feb2..7b5a97d 100644 --- a/Sources/src/main/kotlin/allin/Application.kt +++ b/Sources/src/main/kotlin/allin/Application.kt @@ -58,6 +58,7 @@ private fun Application.extracted() { } } } + install(ContentNegotiation) { json() } install(SwaggerUI) { swagger { diff --git a/Sources/src/main/kotlin/allin/data/BetDataSource.kt b/Sources/src/main/kotlin/allin/data/BetDataSource.kt index f8f8441..f0c1e03 100644 --- a/Sources/src/main/kotlin/allin/data/BetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/BetDataSource.kt @@ -17,4 +17,6 @@ interface BetDataSource { fun getWonNotifications(username: String): List fun getHistory(username: String): List fun getCurrent(username: String): List + fun getMostPopularBet(): Bet? + fun updatePopularityScore(betId: String) } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt index 123768b..7c522ea 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt @@ -1,8 +1,15 @@ package allin.data.mock 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.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 kotlin.math.roundToInt @@ -199,4 +206,15 @@ class MockBetDataSource(private val mockData: MockDataSource.MockData) : BetData 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 + } + } diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt index cbd3090..d19155e 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt @@ -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) { database.bets.add( BetEntity { @@ -205,4 +223,5 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource { } } } + } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt index 3e08eff..41b075b 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt @@ -48,7 +48,8 @@ class PostgresDataSource : AllInDataSource() { isprivate boolean, createdby varchar(250), status varchar(20), - type varchar(20) + type varchar(20), + popularityscore numeric ) """.trimIndent() ) diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt index 932cda8..31a51f8 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt @@ -40,15 +40,17 @@ class PostgresUserDataSource(private val database: Database) : UserDataSource { database.users.removeIf { (it.username eq username) or (it.email eq username) } > 0 override fun addCoins(username: String, amount: Int) { - database.users - .find { it.username eq username } - ?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins + amount) + database.update(UsersEntity) { + set(it.nbCoins, it.nbCoins + amount) + where { it.username eq username } + } } override fun removeCoins(username: String, amount: Int) { - database.users - .find { it.username eq username } - ?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins - amount) + database.update(UsersEntity) { + set(it.nbCoins, it.nbCoins - amount) + where { it.username eq username } + } } override fun userExists(username: String) = diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt index ad68238..31949eb 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt @@ -5,6 +5,7 @@ import org.ktorm.database.Database import org.ktorm.dsl.eq import org.ktorm.entity.* import org.ktorm.schema.* +import org.postgresql.util.ByteConverter.numeric import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime @@ -23,6 +24,7 @@ interface BetEntity : Entity { var status: BetStatus var type: BetType var createdBy: String + var popularityscore: Int fun toBet(database: Database) = Bet( @@ -39,7 +41,8 @@ interface BetEntity : Entity { } else { database.responses.filter { it.betId eq id }.map { it.response } }, - createdBy = createdBy + createdBy = createdBy, + popularityscore = popularityscore, ) fun toBetDetail(database: Database, username: String): BetDetail { @@ -60,6 +63,7 @@ interface BetEntity : Entity { ) } + } object BetsEntity : Table("bet") { @@ -73,6 +77,7 @@ object BetsEntity : Table("bet") { val status = enum("status").bindTo { it.status } val type = enum("type").bindTo { it.type } val createdBy = varchar("createdby").bindTo { it.createdBy } + val popularityscore = int("popularityscore").bindTo { it.popularityscore } } val Database.bets get() = this.sequenceOf(BetsEntity) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/Bet.kt b/Sources/src/main/kotlin/allin/model/Bet.kt index 23eb638..07a8360 100644 --- a/Sources/src/main/kotlin/allin/model/Bet.kt +++ b/Sources/src/main/kotlin/allin/model/Bet.kt @@ -19,7 +19,8 @@ data class Bet( @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, var isPrivate: Boolean, var response: List, - val createdBy: String = "" + val createdBy: String = "", + var popularityscore: Int ) @Serializable diff --git a/Sources/src/main/kotlin/allin/routing/betRouter.kt b/Sources/src/main/kotlin/allin/routing/betRouter.kt index 57a810c..d235998 100644 --- a/Sources/src/main/kotlin/allin/routing/betRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/betRouter.kt @@ -53,7 +53,7 @@ fun Application.betRouter() { betDataSource.getBetById(id)?.let { call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST) } ?: 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) 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("JWT token of the logged user") + } + response { + HttpStatusCode.Accepted to { + description = "The most popular public bet is available" + body { + 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}", { description = "Retrieves a specific bet" request { diff --git a/Sources/src/main/kotlin/allin/routing/participationRouter.kt b/Sources/src/main/kotlin/allin/routing/participationRouter.kt index 43016b6..1e0aa8c 100644 --- a/Sources/src/main/kotlin/allin/routing/participationRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/participationRouter.kt @@ -22,6 +22,7 @@ fun Application.participationRouter() { val userDataSource = this.dataSource.userDataSource val participationDataSource = this.dataSource.participationDataSource + val betDataSource = this.dataSource.betDataSource routing { authenticate { @@ -47,6 +48,11 @@ fun Application.participationRouter() { hasToken { principal -> val participation = call.receive() verifyUserFromToken(userDataSource, principal) { user, _ -> + + if(betDataSource.getBetById(participation.betId)== null){ + call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) + } + if (user.nbCoins >= participation.stake) { participationDataSource.addParticipation( Participation( @@ -59,6 +65,7 @@ fun Application.participationRouter() { ) userDataSource.removeCoins(username = user.username, amount = participation.stake) + betDataSource.updatePopularityScore(participation.betId) call.respond(HttpStatusCode.Created) } else { call.respond(HttpStatusCode.Forbidden, ApiMessage.NOT_ENOUGH_COINS) diff --git a/Sources/src/main/kotlin/allin/routing/userRouter.kt b/Sources/src/main/kotlin/allin/routing/userRouter.kt index d37b4e6..ace7d8b 100644 --- a/Sources/src/main/kotlin/allin/routing/userRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/userRouter.kt @@ -56,24 +56,25 @@ fun Application.userRouter() { if (RegexCheckerUser.isEmailInvalid(tempUser.email)) { 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) } - if(userDataSource.emailExists(tempUser.email)){ + else if (userDataSource.emailExists(tempUser.email)) { 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", {