diff --git a/Sources/src/main/kotlin/allin/data/BetDataSource.kt b/Sources/src/main/kotlin/allin/data/BetDataSource.kt index 0596a86..19a4d3d 100644 --- a/Sources/src/main/kotlin/allin/data/BetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/BetDataSource.kt @@ -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 @@ -14,4 +16,7 @@ interface BetDataSource { fun updateBetStatuses(date: ZonedDateTime) fun getToConfirm(username: String): List fun confirmBet(betId: String, result: String) + fun getWonNotifications(username: String): List + fun getHistory(username: String): List + fun getCurrent(username: String): List } \ 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 bae246b..ca8e046 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt @@ -1,13 +1,16 @@ package allin.data.mock import allin.data.BetDataSource -import allin.model.Bet -import allin.model.BetResult -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 = bets override fun getBetById(id: String): Bet? = bets.find { it.id == id } @@ -32,16 +35,16 @@ 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) } } } } override fun getToConfirm(username: String): List = - bets.filter { it.createdBy == username && it.status == BetStatus.CLOSING } + bets.filter { it.createdBy == username && it.status == CLOSING } override fun confirmBet(betId: String, result: String) { results.add( @@ -52,12 +55,71 @@ class MockBetDataSource : BetDataSource { ) bets.replaceAll { if (it.id == betId) { - it.copy(status = BetStatus.FINISHED) + 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 { + 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 } } - private val bets by lazy { mutableListOf() } - private val results by lazy { mutableListOf() } + override fun getHistory(username: String): List { + 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 -} \ No newline at end of file + BetResultDetail( + betResult = result, + bet = bet, + participation = participation, + amount = participation.stake, + won = participation.answer == result.result + ) + }.mapNotNull { it } + } + + override fun getCurrent(username: String): List { + 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 } + } +} diff --git a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt index 1cd0b9d..998f05c 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt @@ -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() } + val results by lazy { mutableListOf() } + val resultNotifications by lazy { mutableListOf>() } + val users by lazy { mutableListOf() } + val lastGifts by lazy { mutableMapOf() } + val participations by lazy { mutableListOf() } + } + + private val mockData by lazy { MockData() } + + override val userDataSource: UserDataSource = MockUserDataSource(mockData) + override val betDataSource: BetDataSource = MockBetDataSource(mockData) + override val participationDataSource: ParticipationDataSource = MockParticipationDataSource(mockData) } diff --git a/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt index c9f22af..39fc209 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockParticipationDataSource.kt @@ -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() } - } \ 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 b6e7a2b..42883b9 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockUserDataSource.kt @@ -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 = 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() - } - - private val lastGifts by lazy { - mutableMapOf() - } } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt index ebbb202..5bc55f0 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt @@ -3,10 +3,7 @@ package allin.data.postgres import allin.data.BetDataSource import allin.data.postgres.entities.* import allin.data.postgres.entities.ResponsesEntity.response -import allin.model.Bet -import allin.model.BetStatus -import allin.model.BetType -import allin.model.UpdatedBetData +import allin.model.* import org.ktorm.database.Database import org.ktorm.dsl.* import java.time.ZoneId @@ -40,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 = database.from(BetsEntity).select().mapToBet() @@ -77,6 +96,73 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource { 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 { + 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 { + 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 { + 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) { diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt index fc04b55..32adc22 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt @@ -58,6 +58,11 @@ class PostgresDataSource : AllInDataSource() { 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() ) 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 4b1d70a..cbaafc3 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetResultEntity.kt @@ -4,6 +4,7 @@ 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 { @@ -14,4 +15,14 @@ interface BetResultEntity : Entity { object BetResultsEntity : Table("betresult") { val betId = uuid("betid").primaryKey().references(BetsEntity) { it.bet } val result = varchar("result").bindTo { it.result } +} + +interface BetResultNotificationEntity : Entity { + val betId: UUID + val username: String +} + +object BetResultNotificationsEntity : Table("betresult") { + val betId = uuid("betid").primaryKey() + val username = varchar("username").primaryKey() } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/BetResultDetail.kt b/Sources/src/main/kotlin/allin/model/BetResultDetail.kt new file mode 100644 index 0000000..82fdb6a --- /dev/null +++ b/Sources/src/main/kotlin/allin/model/BetResultDetail.kt @@ -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 +) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/ParticipationDetail.kt b/Sources/src/main/kotlin/allin/model/ParticipationDetail.kt deleted file mode 100644 index 04ba74b..0000000 --- a/Sources/src/main/kotlin/allin/model/ParticipationDetail.kt +++ /dev/null @@ -1,12 +0,0 @@ -package allin.model - -import kotlinx.serialization.Serializable - -@Serializable -data class ParticipationDetail( - val id: String, - val bet: Bet, - val username: String, - val answer: String, - val stake: Int -) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/BetRouter.kt b/Sources/src/main/kotlin/allin/routing/BetRouter.kt index 96bdd15..f0b70ed 100644 --- a/Sources/src/main/kotlin/allin/routing/BetRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/BetRouter.kt @@ -52,25 +52,6 @@ fun Application.BetRouter() { } } - 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) - } - } - } - } - route("/bets/get/{id}") { get { val id = call.parameters["id"] ?: "" @@ -102,18 +83,50 @@ fun Application.BetRouter() { } } + 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)) + } + } + } + } + authenticate { 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)) } } } diff --git a/Sources/src/main/resources/application.conf b/Sources/src/main/resources/application.conf index 1b4a12c..1613b79 100644 --- a/Sources/src/main/resources/application.conf +++ b/Sources/src/main/resources/application.conf @@ -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" \ No newline at end of file +realm="allin" \ No newline at end of file