diff --git a/Sources/src/main/kotlin/allin/data/BetDataSource.kt b/Sources/src/main/kotlin/allin/data/BetDataSource.kt index f0c1e03..bcffd62 100644 --- a/Sources/src/main/kotlin/allin/data/BetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/BetDataSource.kt @@ -1,10 +1,11 @@ package allin.data +import allin.dto.UserDTO import allin.model.* import java.time.ZonedDateTime interface BetDataSource { - fun getAllBets(filters: List): List + fun getAllBets(filters: List, userDTO: UserDTO): List fun getBetById(id: String): Bet? fun getBetDetailById(id: String, username: String): BetDetail? fun getBetsNotFinished(): List @@ -19,4 +20,6 @@ interface BetDataSource { fun getCurrent(username: String): List fun getMostPopularBet(): Bet? fun updatePopularityScore(betId: String) + fun addPrivateBet(bet: Bet) + fun isInvited(betid: String, userId: String): Boolean } \ 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 1f757f0..c9831dd 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockBetDataSource.kt @@ -1,6 +1,7 @@ package allin.data.mock import allin.data.BetDataSource +import allin.dto.UserDTO import allin.model.* import allin.model.BetStatus.* import java.time.ZonedDateTime @@ -15,7 +16,7 @@ class MockBetDataSource(private val mockData: MockDataSource.MockData) : BetData private val betInfos get() = mockData.betInfos private val answerInfos get() = mockData.answerInfos - override fun getAllBets(filters: List): List { + override fun getAllBets(filters: List, userDTO: UserDTO): List { return when { filters.isEmpty() -> bets @@ -233,4 +234,12 @@ class MockBetDataSource(private val mockData: MockDataSource.MockData) : BetData bet.popularityscore = score } + override fun addPrivateBet(bet: Bet) { + TODO() + } + + override fun isInvited(betid: String, userId: String): Boolean { + TODO("Not yet implemented") + } + } diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt index 35c0fd1..3ff7ffc 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresBetDataSource.kt @@ -2,6 +2,7 @@ package allin.data.postgres import allin.data.BetDataSource import allin.data.postgres.entities.* +import allin.dto.UserDTO import allin.model.* import org.ktorm.database.Database import org.ktorm.dsl.* @@ -13,9 +14,10 @@ import kotlin.math.roundToInt class PostgresBetDataSource(private val database: Database) : BetDataSource { - override fun getAllBets(filters: List): List { + override fun getAllBets(filters: List, userDTO: UserDTO): List { return when { filters.isEmpty() -> database.bets.map { it.toBet(database) } + .filter { (!it.isPrivate) or (isInvited(it.id, userDTO.id)) or (it.createdBy == userDTO.id) } filters.size == 1 -> { val filter = filters.first() @@ -28,6 +30,7 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource { it.status inList listOf(BetStatus.IN_PROGRESS, BetStatus.WAITING, BetStatus.CLOSING) } }.map { it.toBet(database) } + .filter { (!it.isPrivate) or (isInvited(it.id, userDTO.id)) or (it.createdBy == userDTO.id) } } else -> { @@ -44,11 +47,11 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource { (public or invitation) and (finished or inProgress) }.map { it.toBet(database) } + .filter { (!it.isPrivate) or (isInvited(it.id, userDTO.id)) or (it.createdBy == userDTO.id) } } } } - override fun getBetById(id: String): Bet? = database.bets.find { it.id eq id }?.toBet(database) @@ -269,4 +272,17 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource { } } + override fun addPrivateBet(bet: Bet) { + addBet(bet) + bet.userInvited?.forEach { + database.privatebets.add(PrivateBetEntity { + betId = bet.id + userId = it + }) + } + } + + override fun isInvited(betid: String, userId: String): Boolean { + return database.privatebets.filter { (it.betid eq betid) and (it.userId eq userId) }.isNotEmpty() + } } \ 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 3f3e52a..14cfc49 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt @@ -144,6 +144,17 @@ class PostgresDataSource : AllInDataSource() { ) """.trimIndent() ) + + database.execute( + """ + CREATE TABLE IF NOT EXISTS privatebet + ( + betid VARCHAR(255), + userid VARCHAR(255), + CONSTRAINT pk_privatebet PRIMARY KEY (betid,userid) + ) + """.trimIndent() + ) } override val userDataSource: UserDataSource by lazy { PostgresUserDataSource(database) } 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 41990fa..4627f2f 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/BetEntity.kt @@ -41,7 +41,7 @@ interface BetEntity : Entity { } else { database.responses.filter { it.betId eq id }.map { it.response } }, - createdBy = createdBy, + createdBy = database.users.first { it.id eq createdBy }.username, popularityscore = popularityscore, totalStakes = betInfo?.totalStakes ?: 0, totalParticipants = betInfo?.totalParticipants ?: 0 diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/PrivateBetEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/PrivateBetEntity.kt new file mode 100644 index 0000000..3417b6f --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/PrivateBetEntity.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 PrivateBetEntity : Entity { + companion object : Entity.Factory() + + var betId: String + var userId: String +} + +object PrivateBetsEntity : Table("privatebet") { + val betid = varchar("betid").bindTo { it.betId } + val userId = varchar("userid").bindTo { it.userId } +} + +val Database.privatebets get() = this.sequenceOf(PrivateBetsEntity) \ 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 e48a6d6..cd9353b 100644 --- a/Sources/src/main/kotlin/allin/model/Bet.kt +++ b/Sources/src/main/kotlin/allin/model/Bet.kt @@ -22,7 +22,8 @@ data class Bet( val createdBy: String = "", var popularityscore: Int = 0, val totalStakes: Int = 0, - val totalParticipants: Int = 0 + val totalParticipants: Int = 0, + val userInvited: List? = null ) @Serializable diff --git a/Sources/src/main/kotlin/allin/routing/betRouter.kt b/Sources/src/main/kotlin/allin/routing/betRouter.kt index edd7f69..3fea20c 100644 --- a/Sources/src/main/kotlin/allin/routing/betRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/betRouter.kt @@ -48,19 +48,22 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","POST /bets/add") + logManager.log("Routing", "POST /bets/add") hasToken { principal -> val bet = call.receive() val id = UUID.randomUUID().toString() val username = tokenManagerBet.getUsernameFromToken(principal) val user = userDataSource.getUserByUsername(username) betDataSource.getBetById(id)?.let { - logManager.log("Routing","${ApiMessage.BET_ALREADY_EXIST} /bets/add") + logManager.log("Routing", "${ApiMessage.BET_ALREADY_EXIST} /bets/add") call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST) } ?: run { - val betWithId = bet.copy(id = id, createdBy = user.first?.username.toString()) - betDataSource.addBet(betWithId) - logManager.log("Routing","CREATED /bets/add\t${betWithId}") + val betWithId = bet.copy(id = id, createdBy = user.first?.id.toString()) + + if (bet.isPrivate && bet.userInvited?.isNotEmpty() == true) { + betDataSource.addPrivateBet(betWithId) + } else betDataSource.addBet(betWithId) + logManager.log("Routing", "CREATED /bets/add\t${betWithId}") call.respond(HttpStatusCode.Created, betWithId) } } @@ -84,15 +87,15 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","POST /bets/gets") + logManager.log("Routing", "POST /bets/gets") hasToken { principal -> - verifyUserFromToken(userDataSource, principal) { _, _ -> + verifyUserFromToken(userDataSource, principal) { user, _ -> val filtersRequest = kotlin.runCatching { call.receiveNullable() }.getOrNull() val filters = filtersRequest?.filters ?: emptyList() // Use provided filters or empty list if null - logManager.log("Routing","ACCEPTED /bets/gets\t${filters}") - call.respond(HttpStatusCode.Accepted, betDataSource.getAllBets(filters)) + logManager.log("Routing", "ACCEPTED /bets/gets\t${filters}") + call.respond(HttpStatusCode.Accepted, betDataSource.getAllBets(filters, user)) } } } @@ -113,15 +116,15 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/popular") + logManager.log("Routing", "GET /bets/popular") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { _, _ -> val bet = betDataSource.getMostPopularBet() if (bet != null) { - logManager.log("Routing","ACCEPTED /bets/popular\t${bet}") + logManager.log("Routing", "ACCEPTED /bets/popular\t${bet}") call.respond(HttpStatusCode.Accepted, bet) } - logManager.log("Routing","${ApiMessage.BET_NOT_FOUND} /bets/popular") + logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/popular") call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) } } @@ -146,13 +149,12 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/get/{id}") + logManager.log("Routing", "GET /bets/get/{id}") val id = call.parameters["id"] ?: "" betDataSource.getBetById(id)?.let { bet -> - logManager.log("Routing","ACCEPTED /bets/get/{id}\t ${bet}") + logManager.log("Routing", "ACCEPTED /bets/get/{id}\t ${bet}") call.respond(HttpStatusCode.Accepted, bet) - } ?: - logManager.log("Routing","${ApiMessage.BET_NOT_FOUND} /bets/get/{id}") + } ?: logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/get/{id}") call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) } @@ -173,13 +175,13 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","POST /bets/delete") + logManager.log("Routing", "POST /bets/delete") val id = call.receive>()["id"] ?: "" if (betDataSource.removeBet(id)) { - logManager.log("Routing","ACCEPTED /bets/delete") + logManager.log("Routing", "ACCEPTED /bets/delete") call.respond(HttpStatusCode.Accepted) } else { - logManager.log("Routing","${ApiMessage.BET_NOT_FOUND} /bets/delete") + logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/delete") call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) } } @@ -201,13 +203,13 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","POST /bets/update") + logManager.log("Routing", "POST /bets/update") val updatedBetData = call.receive() if (betDataSource.updateBet(updatedBetData)) { - logManager.log("Routing","ACCEPTED /bets/delete") + logManager.log("Routing", "ACCEPTED /bets/delete") call.respond(HttpStatusCode.Accepted) } else { - logManager.log("Routing","${ApiMessage.BET_NOT_FOUND} /bets/delete") + logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/delete") call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND) } } @@ -227,11 +229,11 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/toConfirm") + logManager.log("Routing", "GET /bets/toConfirm") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> val response = betDataSource.getToConfirm(user.username) - logManager.log("Routing","ACCEPTED /bets/toConfirm\t${response}") + logManager.log("Routing", "ACCEPTED /bets/toConfirm\t${response}") call.respond(HttpStatusCode.Accepted, response) } } @@ -253,10 +255,10 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/getWon") + logManager.log("Routing", "GET /bets/getWon") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> - logManager.log("Routing","ACCEPTED /bets/getWon") + logManager.log("Routing", "ACCEPTED /bets/getWon") call.respond(HttpStatusCode.Accepted, betDataSource.getWonNotifications(user.username)) } } @@ -278,10 +280,13 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/history") + logManager.log("Routing", "GET /bets/history") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> - logManager.log("Routing","ACCEPTED /bets/toConfirm\t${betDataSource.getHistory(user.username)}") + logManager.log( + "Routing", + "ACCEPTED /bets/toConfirm\t${betDataSource.getHistory(user.username)}" + ) call.respond(HttpStatusCode.Accepted, betDataSource.getHistory(user.username)) } } @@ -303,10 +308,13 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/current") + logManager.log("Routing", "GET /bets/current") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> - logManager.log("Routing","ACCEPTED /bets/toConfirm\t${betDataSource.getCurrent(user.username)}") + logManager.log( + "Routing", + "ACCEPTED /bets/toConfirm\t${betDataSource.getCurrent(user.username)}" + ) call.respond(HttpStatusCode.Accepted, betDataSource.getCurrent(user.username)) } } @@ -332,7 +340,7 @@ fun Application.betRouter() { } } }) { - logManager.log("Routing","GET /bets/confirm/{id}") + logManager.log("Routing", "GET /bets/confirm/{id}") hasToken { principal -> verifyUserFromToken(userDataSource, principal) { user, _ -> val betId = call.parameters["id"] ?: "" @@ -340,10 +348,10 @@ fun Application.betRouter() { if (betDataSource.getBetById(betId)?.createdBy == user.username) { betDataSource.confirmBet(betId, result) - logManager.log("Routing","ACCEPTED /bets/confirm/{id}") + logManager.log("Routing", "ACCEPTED /bets/confirm/{id}") call.respond(HttpStatusCode.OK) } else { - logManager.log("Routing","UNAUTHORIZED /bets/confirm/{id}") + logManager.log("Routing", "UNAUTHORIZED /bets/confirm/{id}") call.respond(HttpStatusCode.Unauthorized) } }