diff --git a/Sources/src/main/kotlin/allin/data/BetDataSource.kt b/Sources/src/main/kotlin/allin/data/BetDataSource.kt index 19a4d3d..7aa2762 100644 --- a/Sources/src/main/kotlin/allin/data/BetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/BetDataSource.kt @@ -9,12 +9,13 @@ import java.time.ZonedDateTime interface BetDataSource { fun getAllBets(): List fun getBetById(id: String): Bet? + fun getBetDetailById(id: String, username: String): BetDetail? fun getBetsNotFinished(): List fun addBet(bet: Bet) fun removeBet(id: String): Boolean fun updateBet(data: UpdatedBetData): Boolean fun updateBetStatuses(date: ZonedDateTime) - fun getToConfirm(username: String): List + fun getToConfirm(username: String): List fun confirmBet(betId: String, result: String) fun getWonNotifications(username: String): List fun getHistory(username: String): List diff --git a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt index 6e8ea79..20a20ec 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt @@ -4,20 +4,29 @@ import allin.data.BetDataSource import allin.model.* import allin.model.BetStatus.* import java.time.ZonedDateTime +import kotlin.math.roundToInt -class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { - private val bets by lazy { mockData.bets } - private val results by lazy { mockData.results } - private val users by lazy { mockData.users } - private val participations by lazy { mockData.participations } - private val resultNotifications by lazy { mockData.resultNotifications } +class MockBetDataSource(private val mockData: MockDataSource.MockData) : BetDataSource { + private val bets get() = mockData.bets + private val results get() = mockData.results + private val users get() = mockData.users + private val participations get() = mockData.participations + private val resultNotifications get() = mockData.resultNotifications + private val betInfos get() = mockData.betInfos + private val answerInfos get() = mockData.answerInfos override fun getAllBets(): List = bets override fun getBetById(id: String): Bet? = bets.find { it.id == id } - override fun removeBet(id: String): Boolean = - bets.removeIf { it.id == id } + override fun getBetDetailById(id: String, username: String): BetDetail? = + bets.find { it.id == id }?.toBetDetail(username) + + override fun removeBet(id: String): Boolean { + betInfos.removeIf { it.id == id } + answerInfos.removeIf { it.betId == id } + return bets.removeIf { it.id == id } + } override fun updateBet(data: UpdatedBetData): Boolean { return bets.find { it.id == data.id }?.let { @@ -30,6 +39,15 @@ class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { override fun addBet(bet: Bet) { bets += bet + betInfos += BetInfo(id = bet.id, totalStakes = 0) + bet.response.forEach { + answerInfos += BetAnswerInfo( + betId = bet.id, + response = it, + totalStakes = 0, + odds = 1f + ) + } } override fun updateBetStatuses(date: ZonedDateTime) { @@ -46,8 +64,9 @@ class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { } } - override fun getToConfirm(username: String): List = + override fun getToConfirm(username: String): List = bets.filter { it.createdBy == username && it.status == CLOSING } + .map { it.toBetDetail(username) } override fun confirmBet(betId: String, result: String) { results.add( @@ -82,11 +101,12 @@ class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { if (participation.answer == result.result) { resultNotifications.remove(notification) + val answerInfo = answerInfos.find { it.betId == bet.id && it.response == participation.answer } BetResultDetail( betResult = result, bet = bet, participation = participation, - amount = participation.stake, + amount = (participation.stake * (answerInfo?.odds ?: 1f)).roundToInt(), won = true ) } else null @@ -99,35 +119,49 @@ class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { val participation = participations.find { it.username == username && it.betId == bet.id } ?: return@map null + val won = participation.answer == result.result + val answerInfo = answerInfos.find { + it.betId == bet.id && it.response == participation.answer + } + BetResultDetail( betResult = result, bet = bet, participation = participation, - amount = participation.stake, - won = participation.answer == result.result + amount = if (won) { + (participation.stake * (answerInfo?.odds ?: 1f)).roundToInt() + } else participation.stake, + won = won ) }.mapNotNull { it } } override fun getCurrent(username: String): List { - return bets.map { bet -> + return bets.mapNotNull { bet -> when (bet.status) { - CANCELLED, FINISHED -> return@map null + CANCELLED, FINISHED -> return@mapNotNull 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 - ) + val userParticipation = participations.find { it.username == username && it.betId == bet.id } + if (userParticipation == null) return@mapNotNull null + return@mapNotNull bet.toBetDetail(username) } } - }.mapNotNull { it } + } + } + + private fun Bet.toBetDetail(username: String): BetDetail { + val participation = participations.find { it.username == username && it.betId == this.id } + val participations = participations.filter { it.betId == this.id } + + return BetDetail( + bet = this, + answers = getBetAnswerDetail( + bet = this, + participations = participations, + infos = answerInfos.filter { it.betId == this.id } + ), + participations = participations, + userParticipation = participation + ) } } diff --git a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt index 36517cc..5b93834 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt @@ -1,10 +1,10 @@ package allin.data.mock -import allin.data.* -import allin.model.Bet -import allin.model.BetResult -import allin.model.Participation -import allin.model.User +import allin.data.AllInDataSource +import allin.data.BetDataSource +import allin.data.ParticipationDataSource +import allin.data.UserDataSource +import allin.model.* import java.time.ZonedDateTime class MockDataSource : AllInDataSource() { @@ -15,6 +15,8 @@ class MockDataSource : AllInDataSource() { class MockData { val bets by lazy { mutableListOf() } + val betInfos by lazy { mutableListOf() } + val answerInfos by lazy { mutableListOf() } val results by lazy { mutableListOf() } val resultNotifications by lazy { mutableListOf>() } val users by lazy { mutableListOf() } @@ -27,5 +29,4 @@ class MockDataSource : AllInDataSource() { override val userDataSource: UserDataSource by lazy { MockUserDataSource(mockData) } override val betDataSource: BetDataSource by lazy { MockBetDataSource(mockData) } override val participationDataSource: ParticipationDataSource by lazy { MockParticipationDataSource(mockData) } - override val friendDataSource: FriendDataSource by lazy { MockFriendDataSource(mockData) } } diff --git a/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt index 632891b..653191a 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt @@ -3,11 +3,41 @@ package allin.data.mock import allin.data.ParticipationDataSource import allin.model.Participation -class MockParticipationDataSource(mockData: MockDataSource.MockData) : ParticipationDataSource { - private val participations by lazy { mockData.participations } +class MockParticipationDataSource(private val mockData: MockDataSource.MockData) : ParticipationDataSource { + private val participations get() = mockData.participations + private val betInfos get() = mockData.betInfos + private val answerInfos get() = mockData.answerInfos override fun addParticipation(participation: Participation) { participations += participation + var betTotalStakes = 0 + + betInfos.replaceAll { + if (participation.betId == it.id) { + betTotalStakes = it.totalStakes + participation.stake + it.copy(totalStakes = betTotalStakes) + } else { + it + } + } + + answerInfos.replaceAll { + if (participation.betId == it.betId) { + if (participation.answer == it.response) { + val answerTotalStakes = it.totalStakes + participation.stake + val probability = answerTotalStakes / betTotalStakes.toFloat() + it.copy( + totalStakes = answerTotalStakes, + odds = 1 / probability + ) + } else { + val probability = it.totalStakes / betTotalStakes.toFloat() + it.copy(odds = 1 / probability) + } + } else { + it + } + } } override fun getParticipationFromBetId(betid: String): List = @@ -16,6 +46,39 @@ class MockParticipationDataSource(mockData: MockDataSource.MockData) : Participa override fun getParticipationFromUserId(username: String, betid: String): List = participations.filter { it.betId == betid && it.username == username } - override fun deleteParticipation(id: String): Boolean = - participations.removeIf { it.id == id } + override fun deleteParticipation(id: String): Boolean { + val participation = participations.find { it.id == id } + val result = participations.remove(participation) + var betTotalStakes = 0 + + betInfos.replaceAll { + if (participation?.betId == it.id) { + betTotalStakes = it.totalStakes - participation.stake + it.copy(totalStakes = betTotalStakes) + } else { + it + } + } + + answerInfos.replaceAll { + if (participation?.betId == it.betId) { + if (participation.answer == it.response) { + val answerTotalStakes = it.totalStakes - participation.stake + val probability = answerTotalStakes / betTotalStakes.toFloat() + it.copy( + totalStakes = answerTotalStakes, + odds = 1 / probability + ) + } else { + val probability = it.totalStakes / betTotalStakes.toFloat() + it.copy(odds = 1 / probability) + } + } else { + it + } + } + + return result + } + } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt index 9bd6f11..1f56207 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt @@ -5,9 +5,9 @@ import allin.dto.UserDTO import allin.model.User import java.time.ZonedDateTime -class MockUserDataSource(mockData: MockDataSource.MockData) : UserDataSource { - private val users by lazy { mockData.users } - private val lastGifts by lazy { mockData.lastGifts } +class MockUserDataSource(private val mockData: MockDataSource.MockData) : UserDataSource { + private val users get() = mockData.users + private val lastGifts get() = mockData.lastGifts override fun getUserByUsername(username: String): Pair = users.find { it.username == username }?.let { diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt index 72501cf..f650a69 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt @@ -2,193 +2,146 @@ package allin.data.postgres import allin.data.BetDataSource 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 org.ktorm.entity.* import java.time.ZoneId import java.time.ZonedDateTime class PostgresBetDataSource(private val database: Database) : BetDataSource { - private fun QueryRowSet.toBet() = - Bet( - id = this[BetsEntity.id].toString(), - theme = this[BetsEntity.theme].toString(), - sentenceBet = this[BetsEntity.sentenceBet].toString(), - endRegistration = this[BetsEntity.endRegistration]!!.atZone(ZoneId.of("Europe/Paris")), - endBet = this[BetsEntity.endBet]!!.atZone(ZoneId.of("Europe/Paris")), - isPrivate = this[BetsEntity.isPrivate] ?: false, - status = this[BetsEntity.status] ?: BetStatus.IN_PROGRESS, - type = this[BetsEntity.type] ?: BetType.CUSTOM, - createdBy = this[BetsEntity.createdBy].toString(), - response = let { - val idBet = this[BetsEntity.id].toString() - val type = this[BetsEntity.type] ?: BetType.CUSTOM - if (type == BetType.CUSTOM) { - database.from(ResponsesEntity) - .select(response) - .where { ResponsesEntity.id eq idBet } - .map { it[response].toString() } - } else { - listOf(YES_VALUE, NO_VALUE) - } - } - ) - - 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 = - database.from(BetsEntity).select().mapToBet() + database.bets.map { it.toBet(database) } override fun getBetById(id: String): Bet? = - database.from(BetsEntity).select().where { - BetsEntity.id eq id - }.mapToBet().firstOrNull() + database.bets.find { it.id eq id }?.toBet(database) + + override fun getBetDetailById(id: String, username: String): BetDetail? = + database.bets.find { it.id eq id }?.toBetDetail(database, username) override fun getBetsNotFinished(): List { val currentTime = ZonedDateTime.now(ZoneId.of("Europe/Paris")) - return database.from(BetsEntity) - .select() - .where { BetsEntity.endBet greaterEq currentTime.toInstant() } - .mapToBet() + return database.bets + .filter { it.endBet greaterEq currentTime.toInstant() } + .map { it.toBet(database) } } - override fun getToConfirm(username: String): List { - return database.from(BetsEntity) - .select() - .where { - (BetsEntity.createdBy eq username) and - (BetsEntity.status eq BetStatus.CLOSING) - }.mapToBet() + override fun getToConfirm(username: String): List { + return database.bets + .filter { + (it.createdBy eq username) and (BetsEntity.status eq BetStatus.CLOSING) + } + .map { it.toBetDetail(database, username) } } 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 betId } - set(BetsEntity.status, BetStatus.FINISHED) + database.bets.find { it.id eq betId }?.let { bet -> + bet.status = BetStatus.FINISHED + bet.flushChanges() + + database.betResults.add( + BetResultEntity { + this.bet = bet + this.result = result + } + ) } - database.from(ParticipationsEntity) - .select() - .where { - (ParticipationsEntity.betId eq betId) and - (ParticipationsEntity.answer eq result) - } - .forEach { participation -> - database.insert(BetResultNotificationsEntity) { - set(it.betId, betId) - set(it.username, participation[ParticipationsEntity.username]) + database.participations.filter { + (ParticipationsEntity.betId eq betId) and + (ParticipationsEntity.answer eq result) + }.forEach { + database.betResultNotifications.add( + BetResultNotificationEntity { + this.betId = betId + this.username = it.username } - } + ) + } } override fun getWonNotifications(username: String): List { - 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) - } + return database.betResultNotifications + .filter { it.username eq username } + .flatMap { notif -> + notif.delete() + + database.participations + .filter { + (it.username eq username) and + (it.betId eq notif.betId) } - } - it - }.mapToBetResultDetail() + .mapNotNull { participation -> + database.betResults + .find { it.betId eq participation.bet.id } + ?.toBetResultDetail( + database, + participation + ) + } + + } } override fun getHistory(username: String): List { - 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() + return database.participations + .filter { it.username eq username } + .mapNotNull { participation -> + database.betResults + .find { it.betId eq participation.bet.id } + ?.toBetResultDetail( + database, + participation + ) + } } override fun getCurrent(username: String): List { - 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() - - ) + return database.participations + .filter { it.username eq username } + .mapNotNull { + if (it.bet.status !in listOf(BetStatus.FINISHED, BetStatus.CANCELLED)) { + it.bet.toBetDetail( + database = database, + username = username + ) + } else null } } override fun addBet(bet: Bet) { - database.insert(BetsEntity) { - set(it.id, bet.id) - set(it.endBet, bet.endBet.toInstant()) - set(it.endRegistration, bet.endRegistration.toInstant()) - set(it.sentenceBet, bet.sentenceBet) - set(it.theme, bet.theme) - set(it.isPrivate, bet.isPrivate) - set(it.createdBy, bet.createdBy) - set(it.status, bet.status) - set(it.type, bet.type) - } + database.bets.add( + BetEntity { + this.id = bet.id + this.endBet = bet.endBet.toInstant() + this.endRegistration = bet.endRegistration.toInstant() + this.zoneId = bet.endBet.zone.id + this.sentenceBet = bet.sentenceBet + this.theme = bet.theme + this.isPrivate = bet.isPrivate + this.createdBy = bet.createdBy + this.status = bet.status + this.type = bet.type + } + ) if (bet.type == BetType.CUSTOM) { bet.response.forEach { selected -> - database.insert(ResponsesEntity) { - set(it.id, bet.id) - set(it.response, selected) - } + database.responses.add( + ResponseEntity { + this.betId = bet.id + this.response = selected + } + ) } } } override fun removeBet(id: String): Boolean { - return database.delete(BetsEntity) { it.id eq id } > 0 + database.betInfos.removeIf { it.id eq id } + database.betAnswerInfos.removeIf { it.betId eq id } + return database.bets.removeIf { it.id eq id } > 0 } override fun updateBet(data: UpdatedBetData): Boolean { diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt index 26e15c8..3e08eff 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt @@ -25,7 +25,7 @@ class PostgresDataSource : AllInDataSource() { database.execute( """ - CREATE TABLE IF not exists utilisateur ( + CREATE TABLE IF not exists users ( id VARCHAR(255) PRIMARY KEY, username VARCHAR(255), password VARCHAR(255), @@ -43,6 +43,7 @@ class PostgresDataSource : AllInDataSource() { theme VARCHAR(255), endregistration timestamp, endbet timestamp, + zoneid varchar(500), sentencebet varchar(500), isprivate boolean, createdby varchar(250), @@ -86,9 +87,30 @@ class PostgresDataSource : AllInDataSource() { database.execute( """ CREATE TABLE IF NOT EXISTS response ( - id VARCHAR(255), + betId VARCHAR(255), response VARCHAR(250), - CONSTRAINT pk_response_id PRIMARY KEY (id, response) + CONSTRAINT pk_response_id PRIMARY KEY (betId, response) + ) + """.trimIndent() + ) + + database.execute( + """ + CREATE TABLE IF not exists betInfo ( + id VARCHAR(255) PRIMARY KEY, + totalStakes int + ) + """.trimIndent() + ) + + database.execute( + """ + CREATE TABLE IF not exists betAnswerInfo ( + betId VARCHAR(255), + response VARCHAR(255), + totalStakes int, + odds float, + CONSTRAINT pk_bet_answer_info_id PRIMARY KEY (betId, response) ) """.trimIndent() ) diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresParticipationDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresParticipationDataSource.kt index 79b28f1..4ba7fa8 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresParticipationDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresParticipationDataSource.kt @@ -1,24 +1,16 @@ package allin.data.postgres import allin.data.ParticipationDataSource -import allin.data.postgres.entities.ParticipationsEntity +import allin.data.postgres.entities.* import allin.model.Participation import org.ktorm.database.Database -import org.ktorm.dsl.* +import org.ktorm.dsl.and +import org.ktorm.dsl.eq +import org.ktorm.dsl.insert +import org.ktorm.entity.* class PostgresParticipationDataSource(private val database: Database) : ParticipationDataSource { - private fun QueryRowSet.toParticipation() = - Participation( - id = this[ParticipationsEntity.id].toString(), - betId = this[ParticipationsEntity.betId].toString(), - username = this[ParticipationsEntity.username].toString(), - answer = this[ParticipationsEntity.answer].toString(), - stake = this[ParticipationsEntity.stake] ?: 0, - ) - - private fun Query.mapToParticipation() = this.map { it.toParticipation() } - override fun addParticipation(participation: Participation) { database.insert(ParticipationsEntity) { set(it.id, participation.id) @@ -27,25 +19,50 @@ class PostgresParticipationDataSource(private val database: Database) : Particip set(it.answer, participation.answer) set(it.stake, participation.stake) } + + val betInfo = database.betInfos.find { it.id eq participation.betId } ?: BetInfoEntity { + this.id = participation.betId + this.totalStakes = 0 + } + + betInfo.totalStakes += participation.stake + database.betInfos.update(betInfo) + + database.betAnswerInfos.filter { it.betId eq participation.betId }.forEach { + if (it.response == participation.answer) { + it.totalStakes += participation.stake + } + val probability = it.totalStakes / betInfo.totalStakes.toFloat() + it.odds = 1 / probability + it.flushChanges() + } } override fun getParticipationFromBetId(betid: String): List = - database.from(ParticipationsEntity) - .select() - .where { ParticipationsEntity.betId eq betid } - .mapToParticipation() + database.participations.filter { it.betId eq betid }.map { it.toParticipation() } override fun getParticipationFromUserId(username: String, betid: String): List = - database.from(ParticipationsEntity) - .select() - .where { (ParticipationsEntity.betId eq betid) and (ParticipationsEntity.username eq username) } - .mapToParticipation() - - fun getParticipationEntity(): List = - database.from(ParticipationsEntity).select().mapToParticipation() - - override fun deleteParticipation(id: String): Boolean = - database.delete(ParticipationsEntity) { - it.id eq id - } > 0 -} \ No newline at end of file + database.participations.filter { + (ParticipationsEntity.betId eq betid) and (ParticipationsEntity.username eq username) + }.map { it.toParticipation() } + + override fun deleteParticipation(id: String): Boolean { + val participation = database.participations.find { it.id eq id } ?: return false + database.betInfos.find { it.id eq participation.bet.id }?.let { betInfo -> + betInfo.totalStakes -= participation.stake + + database.betAnswerInfos.filter { it.betId eq participation.bet.id }.forEach { + if (it.response == participation.answer) { + it.totalStakes -= participation.stake + } + val probability = it.totalStakes / betInfo.totalStakes.toFloat() + it.odds = 1 / probability + it.flushChanges() + } + + betInfo.flushChanges() + } + return participation.delete() > 0 + } +} + diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt index 0d0ccec..6e48233 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresUserDataSource.kt @@ -1,70 +1,60 @@ package allin.data.postgres import allin.data.UserDataSource +import allin.data.postgres.entities.UserEntity import allin.data.postgres.entities.UsersEntity +import allin.data.postgres.entities.users import allin.dto.UserDTO import allin.ext.executeWithResult import allin.model.User import org.ktorm.database.Database import org.ktorm.database.use import org.ktorm.dsl.* +import org.ktorm.entity.add +import org.ktorm.entity.filter +import org.ktorm.entity.find +import org.ktorm.entity.removeIf import java.time.Instant.now class PostgresUserDataSource(private val database: Database) : UserDataSource { override fun getUserByUsername(username: String): Pair = - database.from(UsersEntity) - .select() - .where { UsersEntity.username eq username } - .map { row -> - Pair( - UserDTO( - row[UsersEntity.id].toString(), - row[UsersEntity.username].toString(), - row[UsersEntity.email].toString(), - row[UsersEntity.nbCoins] ?: 0, - null - ), - row[UsersEntity.password].toString() - ) - } - .firstOrNull() ?: Pair(null, null) + database.users + .find { it.username eq username } + ?.let { it.toUserDTO() to it.password } + ?: (null to null) override fun addUser(user: User) { - database.insert(UsersEntity) { - set(it.id, user.id) - set(it.nbCoins, user.nbCoins) - set(it.username, user.username) - set(it.password, user.password) - set(it.email, user.email) - set(it.lastGift, now()) - } + database.users.add( + UserEntity { + this.id = user.id + this.nbCoins = user.nbCoins + this.username = user.username + this.password = user.password + this.email = user.email + this.lastGift = now() + } + ) } - override fun deleteUser(username: String): Boolean { - val deletedCount = database.delete(UsersEntity) { - it.username eq username - } - return deletedCount > 0 - } + override fun deleteUser(username: String): Boolean = + database.users.removeIf { it.username eq username } > 0 override fun userExists(username: String, email: String): Boolean { - return database.from(UsersEntity).select(UsersEntity.username, UsersEntity.email).where { - (UsersEntity.username eq username) and (UsersEntity.email eq email) + return database.users.filter { + (it.username eq username) and (it.email eq email) }.totalRecords > 0 } override fun addCoins(username: String, amount: Int) { - database.update(UsersEntity) { - set(UsersEntity.nbCoins, UsersEntity.nbCoins + amount) - where { UsersEntity.username eq username } - } + database.users + .find { it.username eq username } + ?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins + amount) } override fun removeCoins(username: String, amount: Int) { - database.update(UsersEntity) { - set(UsersEntity.nbCoins, UsersEntity.nbCoins - amount) - where { UsersEntity.username eq username } - } + database.users + .find { it.username eq username } + ?.set(UsersEntity.nbCoins.name, UsersEntity.nbCoins - amount) } override fun canHaveDailyGift(username: String): Boolean { diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/BetAnswerInfoEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/BetAnswerInfoEntity.kt new file mode 100644 index 0000000..1eab623 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetAnswerInfoEntity.kt @@ -0,0 +1,36 @@ +package allin.data.postgres.entities + +import allin.model.BetAnswerInfo +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.Table +import org.ktorm.schema.float +import org.ktorm.schema.int +import org.ktorm.schema.varchar + +interface BetAnswerInfoEntity : Entity { + companion object : Entity.Factory() + + var betId: String + var response: String + var totalStakes: Int + var odds: Float + + fun toBetAnswerInfo() = + BetAnswerInfo( + betId = betId, + response = response, + totalStakes = totalStakes, + odds = odds + ) +} + +object BetAnswerInfosEntity : Table("betanswerinfo") { + val betId = varchar("betid").primaryKey().bindTo { it.betId } + val response = varchar("response").primaryKey().bindTo { it.response } + val totalStakes = int("totalstakes").bindTo { it.totalStakes } + val odds = float("odds").bindTo { it.odds } +} + +val Database.betAnswerInfos get() = this.sequenceOf(BetAnswerInfosEntity) 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 04a06e1..ad68238 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt @@ -1,31 +1,78 @@ package allin.data.postgres.entities -import allin.model.BetStatus -import allin.model.BetType -import org.ktorm.entity.Entity +import allin.model.* +import org.ktorm.database.Database +import org.ktorm.dsl.eq +import org.ktorm.entity.* import org.ktorm.schema.* +import java.time.Instant +import java.time.ZoneId import java.time.ZonedDateTime interface BetEntity : Entity { - val theme: String - val sentenceBet: String - val endRegistration: ZonedDateTime - val endBet: ZonedDateTime - val isPrivate: Boolean - val status: BetStatus - val type: BetType - val createdBy: String + companion object : Entity.Factory() + + var id: String + var theme: String + var sentenceBet: String + var endRegistration: Instant + var endBet: Instant + var zoneId: String + var isPrivate: Boolean + var status: BetStatus + var type: BetType + var createdBy: String + + fun toBet(database: Database) = + Bet( + id = id, + theme = theme, + sentenceBet = sentenceBet, + status = status, + type = type, + endRegistration = ZonedDateTime.ofInstant(endRegistration, ZoneId.of(zoneId)), + endBet = ZonedDateTime.ofInstant(endBet, ZoneId.of(zoneId)), + isPrivate = isPrivate, + response = if (type == BetType.BINARY) { + listOf(YES_VALUE, NO_VALUE) + } else { + database.responses.filter { it.betId eq id }.map { it.response } + }, + createdBy = createdBy + ) + + fun toBetDetail(database: Database, username: String): BetDetail { + val bet = this.toBet(database) + val participations = database.participations.filter { it.betId eq bet.id } + val userParticipation = participations.find { it.username eq username } + val participationEntities = participations.map { it.toParticipation() } + + val answerInfos = database.betAnswerInfos + .filter { it.betId eq bet.id } + .map { it.toBetAnswerInfo() } + + return BetDetail( + bet = bet, + answers = getBetAnswerDetail(bet, participationEntities, answerInfos), + participations = participationEntities, + userParticipation = userParticipation?.toParticipation() + + ) + } } object BetsEntity : Table("bet") { - val id = varchar("id").primaryKey() + val id = varchar("id").primaryKey().bindTo { it.id } val theme = varchar("theme").bindTo { it.theme } val sentenceBet = varchar("sentencebet").bindTo { it.sentenceBet } - val endRegistration = timestamp("endregistration") - val endBet = timestamp("endbet") + val endRegistration = timestamp("endregistration").bindTo { it.endRegistration } + val endBet = timestamp("endbet").bindTo { it.endBet } + val zoneId = varchar("zoneid").bindTo { it.zoneId } val isPrivate = boolean("isprivate").bindTo { it.isPrivate } val status = enum("status").bindTo { it.status } val type = enum("type").bindTo { it.type } val createdBy = varchar("createdby").bindTo { it.createdBy } -} \ No newline at end of file +} + +val Database.bets get() = this.sequenceOf(BetsEntity) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/BetInfoEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/BetInfoEntity.kt new file mode 100644 index 0000000..fd2e664 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetInfoEntity.kt @@ -0,0 +1,23 @@ +package allin.data.postgres.entities + +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.Table +import org.ktorm.schema.int +import org.ktorm.schema.varchar + + +interface BetInfoEntity : Entity { + companion object : Entity.Factory() + + var id: String + var totalStakes: Int +} + +object BetInfosEntity : Table("betinfo") { + val id = varchar("id").primaryKey().bindTo { it.id } + val totalStakes = int("totalstakes").bindTo { it.totalStakes } +} + +val Database.betInfos get() = this.sequenceOf(BetInfosEntity) diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt index c350e81..f26412f 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt @@ -1,13 +1,37 @@ package allin.data.postgres.entities +import allin.model.BetResult +import allin.model.BetResultDetail +import org.ktorm.database.Database import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf import org.ktorm.schema.Table import org.ktorm.schema.varchar interface BetResultEntity : Entity { - val bet: BetEntity - val result: String + companion object : Entity.Factory() + + var bet: BetEntity + var result: String + + fun toBetResult() = + BetResult( + betId = bet.id, + result = result + ) + + fun toBetResultDetail( + database: Database, + participationEntity: ParticipationEntity + ) = + BetResultDetail( + betResult = this.toBetResult(), + bet = bet.toBet(database), + participation = participationEntity.toParticipation(), + amount = participationEntity.stake, + won = participationEntity.answer == result + ) } object BetResultsEntity : Table("betresult") { @@ -15,12 +39,4 @@ object BetResultsEntity : Table("betresult") { val result = varchar("result").bindTo { it.result } } -interface BetResultNotificationEntity : Entity { - val betId: String - val username: String -} - -object BetResultNotificationsEntity : Table("betresult") { - val betId = varchar("betid").primaryKey() - val username = varchar("username").primaryKey() -} \ No newline at end of file +val Database.betResults get() = this.sequenceOf(BetResultsEntity) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultNotificationEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultNotificationEntity.kt new file mode 100644 index 0000000..8ee4496 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultNotificationEntity.kt @@ -0,0 +1,21 @@ +package allin.data.postgres.entities + +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.Table +import org.ktorm.schema.varchar + +interface BetResultNotificationEntity : Entity { + companion object : Entity.Factory() + + var betId: String + var username: String +} + +object BetResultNotificationsEntity : Table("betresultnotification") { + val betId = varchar("betid").primaryKey() + val username = varchar("username").primaryKey() +} + +val Database.betResultNotifications get() = this.sequenceOf(BetResultNotificationsEntity) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/ParticipationEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/ParticipationEntity.kt index 1b9362c..222df52 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/ParticipationEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/ParticipationEntity.kt @@ -1,23 +1,38 @@ package allin.data.postgres.entities +import allin.model.Participation +import org.ktorm.database.Database import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf import org.ktorm.schema.Table import org.ktorm.schema.int import org.ktorm.schema.varchar interface ParticipationEntity : Entity { - val id: String - val bet: BetEntity - val username: String - val answer: String - val stake: Int -} + companion object : Entity.Factory() + + var id: String + var bet: BetEntity + var username: String + var answer: String + var stake: Int + fun toParticipation() = + Participation( + id = id, + betId = bet.id, + username = username, + answer = answer, + stake = stake + ) +} object ParticipationsEntity : Table("participation") { val id = varchar("id").primaryKey() val betId = varchar("bet").references(BetsEntity) { it.bet } - val username = varchar("username") - val answer = varchar("answer") - val stake = int("stake") + val username = varchar("username").bindTo { it.username } + val answer = varchar("answer").bindTo { it.answer } + val stake = int("stake").bindTo { it.stake } } + +val Database.participations get() = this.sequenceOf(ParticipationsEntity) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/ResponseEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/ResponseEntity.kt index 7c291ed..4e14f9b 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/ResponseEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/ResponseEntity.kt @@ -1,19 +1,21 @@ package allin.data.postgres.entities +import org.ktorm.database.Database import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf import org.ktorm.schema.Table import org.ktorm.schema.varchar - -const val YES_VALUE = "Yes" -const val NO_VALUE = "No" - interface ResponseEntity : Entity { - val betId: String - val response: String + companion object : Entity.Factory() + + var betId: String + var response: String } object ResponsesEntity : Table("response") { - val id = varchar("id").primaryKey() - val response = varchar("response").primaryKey() + val betId = varchar("betid").primaryKey().bindTo { it.betId } + val response = varchar("response").primaryKey().bindTo { it.response } } + +val Database.responses get() = this.sequenceOf(ResponsesEntity) diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/UserEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/UserEntity.kt index 3d97dbc..463b91e 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/UserEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/UserEntity.kt @@ -1,24 +1,43 @@ package allin.data.postgres.entities +import allin.dto.UserDTO +import org.ktorm.database.Database import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf import org.ktorm.schema.Table import org.ktorm.schema.int import org.ktorm.schema.timestamp import org.ktorm.schema.varchar +import java.time.Instant interface UserEntity : Entity { - val username: String + companion object : Entity.Factory() + + var id: String + var username: String var email: String var password: String var nbCoins: Int + var lastGift: Instant + + fun toUserDTO() = + UserDTO( + id = id, + username = username, + email = email, + nbCoins = nbCoins, + token = null + ) } -object UsersEntity : Table("utilisateur") { - val id = varchar("id").primaryKey() +object UsersEntity : Table("users") { + val id = varchar("id").primaryKey().bindTo { it.id } 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") + val lastGift = timestamp("lastgift").bindTo { it.lastGift } } +val Database.users get() = this.sequenceOf(UsersEntity) + diff --git a/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt b/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt index 3328e43..24e6acf 100644 --- a/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt +++ b/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt @@ -20,7 +20,7 @@ suspend fun PipelineContext<*, ApplicationCall>.verifyUserFromToken( content: suspend (user: UserDTO, password: String) -> Unit ) { val username = principal.payload.getClaim(USERNAME).asString() - val userPassword = userDataSource.getUserByUsername(username) - userPassword.first?.let { content(it, userPassword.second ?: "") } + val user = userDataSource.getUserByUsername(username) + user.first?.let { content(it, user.second ?: "") } ?: call.respond(HttpStatusCode.NotFound, ApiMessage.TOKEN_USER_NOT_FOUND) } \ 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 c6944d6..23eb638 100644 --- a/Sources/src/main/kotlin/allin/model/Bet.kt +++ b/Sources/src/main/kotlin/allin/model/Bet.kt @@ -5,6 +5,9 @@ import allin.serializer.ZonedDateTimeSerializer import kotlinx.serialization.Serializable import java.time.ZonedDateTime +const val YES_VALUE = "Yes" +const val NO_VALUE = "No" + @Serializable data class Bet( val id: String = "", diff --git a/Sources/src/main/kotlin/allin/model/BetDetail.kt b/Sources/src/main/kotlin/allin/model/BetDetail.kt index 757b297..65f31c3 100644 --- a/Sources/src/main/kotlin/allin/model/BetDetail.kt +++ b/Sources/src/main/kotlin/allin/model/BetDetail.kt @@ -19,16 +19,21 @@ data class BetDetail( val userParticipation: Participation? // La participation du User current ) -fun getBetAnswerDetail(bet: Bet, participations: List): List { +fun getBetAnswerDetail( + bet: Bet, + participations: List, + infos: List +): List { return bet.response.map { response -> val responseParticipations = participations.filter { it.answer == response } + val answerInfo = infos.find { it.response == response } + BetAnswerDetail( response = response, - totalStakes = responseParticipations.sumOf { it.stake }, + totalStakes = answerInfo?.totalStakes ?: 0, totalParticipants = responseParticipations.size, highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0, - odds = if (participations.isEmpty()) 1f else responseParticipations.size / participations.size.toFloat() + odds = answerInfo?.odds ?: 1f ) } - } diff --git a/Sources/src/main/kotlin/allin/model/BetInfo.kt b/Sources/src/main/kotlin/allin/model/BetInfo.kt new file mode 100644 index 0000000..a904755 --- /dev/null +++ b/Sources/src/main/kotlin/allin/model/BetInfo.kt @@ -0,0 +1,17 @@ +package allin.model + +import kotlinx.serialization.Serializable + +@Serializable +data class BetInfo( + val id: String, + val totalStakes: Int, +) + +@Serializable +data class BetAnswerInfo( + val betId: String, + val response: String, + val totalStakes: Int, + val odds: Float +) diff --git a/Sources/src/main/kotlin/allin/routing/betDetailRouter.kt b/Sources/src/main/kotlin/allin/routing/betDetailRouter.kt index 65becea..f4cfe4a 100644 --- a/Sources/src/main/kotlin/allin/routing/betDetailRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/betDetailRouter.kt @@ -5,7 +5,6 @@ import allin.ext.hasToken import allin.ext.verifyUserFromToken import allin.model.ApiMessage import allin.model.BetDetail -import allin.model.getBetAnswerDetail import io.github.smiley4.ktorswaggerui.dsl.get import io.ktor.http.* import io.ktor.server.application.* @@ -19,7 +18,6 @@ import java.util.* fun Application.betDetailRouter() { val userDataSource = this.dataSource.userDataSource val betDataSource = this.dataSource.betDataSource - val participationDataSource = this.dataSource.participationDataSource routing { authenticate { @@ -43,17 +41,11 @@ fun Application.betDetailRouter() { hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> val id = call.parameters["id"].toString() - val participations = participationDataSource.getParticipationFromBetId(id) - val selectedBet = betDataSource.getBetById(id) - if (selectedBet != null) { + val result = betDataSource.getBetDetailById(id, user.username) + if (result != null) { call.respond( HttpStatusCode.Accepted, - BetDetail( - selectedBet, - getBetAnswerDetail(selectedBet, participations), - participations.toList(), - participationDataSource.getParticipationFromUserId(user.username, id).lastOrNull() - ) + result ) } else { call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) diff --git a/Sources/src/main/kotlin/allin/routing/betRouter.kt b/Sources/src/main/kotlin/allin/routing/betRouter.kt index 58348d8..2cc737f 100644 --- a/Sources/src/main/kotlin/allin/routing/betRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/betRouter.kt @@ -21,7 +21,6 @@ val tokenManagerBet = AppConfig.tokenManager fun Application.betRouter() { val userDataSource = this.dataSource.userDataSource val betDataSource = this.dataSource.betDataSource - val participationDataSource = this.dataSource.participationDataSource routing { authenticate { @@ -36,7 +35,7 @@ fun Application.betRouter() { response { HttpStatusCode.Created to { description = "the bet has been added" - body() { + body { description = "Bet with assigned id" } } @@ -54,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?.id.toString()) + val betWithId = bet.copy(id = id, createdBy = user.first?.username.toString()) betDataSource.addBet(betWithId) call.respond(HttpStatusCode.Created, betWithId) } @@ -70,7 +69,7 @@ fun Application.betRouter() { response { HttpStatusCode.Accepted to { description = "The list of bets is available" - body>() { + body> { description = "List of all bet in the selected source" } } @@ -167,7 +166,7 @@ fun Application.betRouter() { response { HttpStatusCode.Accepted to { description = "The list of bets that can be validated is available" - body>() { + body> { description = "list of bets that can be validated" } } @@ -175,15 +174,7 @@ fun Application.betRouter() { }) { 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, - participations.find { it.username == user.username } - ) - } + val response = betDataSource.getToConfirm(user.username) call.respond(HttpStatusCode.Accepted, response) } } @@ -199,7 +190,7 @@ fun Application.betRouter() { response { HttpStatusCode.Accepted to { description = "The list of won bets is available" - body>() { + body> { description = "List of won bets" } } @@ -222,7 +213,7 @@ fun Application.betRouter() { response { HttpStatusCode.Accepted to { description = "Bet history is available" - body>() { + body> { description = "Betting history list" } } diff --git a/Sources/src/main/kotlin/allin/routing/participationRouter.kt b/Sources/src/main/kotlin/allin/routing/participationRouter.kt index 5a90501..43016b6 100644 --- a/Sources/src/main/kotlin/allin/routing/participationRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/participationRouter.kt @@ -52,7 +52,7 @@ fun Application.participationRouter() { Participation( id = UUID.randomUUID().toString(), betId = participation.betId, - username = user.id, + username = user.username, answer = participation.answer, stake = participation.stake )