Compare commits
151 Commits
@ -0,0 +1,147 @@
|
|||||||
|
def main(ctx):
|
||||||
|
commit_message = ctx.build.message.lower()
|
||||||
|
if "readme.md" in commit_message or "[no_ci]" in commit_message:
|
||||||
|
return nullPipeline()
|
||||||
|
|
||||||
|
if "[db]" in commit_message:
|
||||||
|
return [
|
||||||
|
ci(ctx),
|
||||||
|
cd(ctx),
|
||||||
|
db(ctx)
|
||||||
|
]
|
||||||
|
else :
|
||||||
|
return [
|
||||||
|
ci(ctx),
|
||||||
|
cd(ctx),
|
||||||
|
]
|
||||||
|
|
||||||
|
def nullPipeline():
|
||||||
|
return {
|
||||||
|
"kind": "pipeline",
|
||||||
|
"name": "Nothing",
|
||||||
|
"steps": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def ci(ctx):
|
||||||
|
CI = {
|
||||||
|
"kind": "pipeline",
|
||||||
|
"name": "CI",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "compilation",
|
||||||
|
"image": "maven:3-openjdk-11",
|
||||||
|
"commands": [
|
||||||
|
"cd Sources",
|
||||||
|
"mvn clean package",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "code-analysis",
|
||||||
|
"image": "openjdk:8-jdk",
|
||||||
|
"commands": [
|
||||||
|
"export SONAR_SCANNER_VERSION=4.7.0.2747",
|
||||||
|
"export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux",
|
||||||
|
"curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip",
|
||||||
|
"unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/",
|
||||||
|
"export PATH=$SONAR_SCANNER_HOME/bin:$PATH",
|
||||||
|
"export SONAR_SCANNER_OPTS=\"-server\"",
|
||||||
|
"sonar-scanner -D sonar.projectKey=Api-Allin -D sonar.sources=./Sources -D sonar.host.url=https://codefirst.iut.uca.fr/sonar",
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"sources": "./src/",
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"SONAR_TOKEN": {"from_secret": "SECRET_TOKEN"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return CI
|
||||||
|
|
||||||
|
def cd(ctx):
|
||||||
|
CD = {
|
||||||
|
"kind": "pipeline",
|
||||||
|
"name": "CD",
|
||||||
|
"volumes": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"temp": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "hadolint",
|
||||||
|
"image": "hadolint/hadolint:latest-alpine",
|
||||||
|
"commands": [
|
||||||
|
"hadolint Sources/Dockerfile"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "docker-image",
|
||||||
|
"image": "plugins/docker",
|
||||||
|
"settings": {
|
||||||
|
"dockerfile": "Sources/Dockerfile",
|
||||||
|
"context": "Sources",
|
||||||
|
"registry": "hub.codefirst.iut.uca.fr",
|
||||||
|
"repo": "hub.codefirst.iut.uca.fr/lucas.evard/api",
|
||||||
|
"username": {"from_secret": "SECRET_REGISTRY_USERNAME"},
|
||||||
|
"password": {"from_secret": "SECRET_REGISTRY_PASSWORD"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deploy-container",
|
||||||
|
"image": "hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest",
|
||||||
|
"environment": {
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_DATA_SOURCE": "postgres",
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_CODEFIRST_CONTAINER": {"from_secret": "CODEFIRST_CONTAINER"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_DB": {"from_secret": "db_database"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_USER": {"from_secret": "db_user"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_PASSWORD": {"from_secret": "db_password"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_HOST": {"from_secret": "db_host"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_SALT": {"from_secret": "SALT"},
|
||||||
|
"ADMINS": "lucasevard,emrekartal,arthurvalin,lucasdelanier",
|
||||||
|
"IMAGENAME": "hub.codefirst.iut.uca.fr/lucas.evard/api:latest",
|
||||||
|
"CONTAINERNAME": "api",
|
||||||
|
"COMMAND": "create",
|
||||||
|
"OVERWRITE": "true",
|
||||||
|
},
|
||||||
|
"depends_on": [
|
||||||
|
"docker-image"
|
||||||
|
],
|
||||||
|
"volumes": [
|
||||||
|
{
|
||||||
|
"name": "images",
|
||||||
|
"path": "/uploads"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return CD
|
||||||
|
|
||||||
|
|
||||||
|
def db(ctx):
|
||||||
|
DB = {
|
||||||
|
"kind": "pipeline",
|
||||||
|
"name": "DB",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "deploy-container-postgres",
|
||||||
|
"image": "hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest",
|
||||||
|
"environment": {
|
||||||
|
"IMAGENAME": "postgres:latest",
|
||||||
|
"CONTAINERNAME": "postgresapi",
|
||||||
|
"COMMAND": "create",
|
||||||
|
"OVERWRITE": "false",
|
||||||
|
"PRIVATE": "false",
|
||||||
|
"ADMINS": "lucasevard,emrekartal,arthurvalin,lucasdelanier",
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_ROOT_PASSWORD": {"from_secret": "db_root_password"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_DB": {"from_secret": "db_database"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_USER": {"from_secret": "db_user"},
|
||||||
|
"CODEFIRST_CLIENTDRONE_ENV_POSTGRES_PASSWORD": {"from_secret": "db_password"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return DB
|
After Width: | Height: | Size: 8.0 KiB |
@ -0,0 +1,8 @@
|
|||||||
|
package allin.data
|
||||||
|
|
||||||
|
abstract class AllInDataSource {
|
||||||
|
abstract val userDataSource: UserDataSource
|
||||||
|
abstract val betDataSource: BetDataSource
|
||||||
|
abstract val participationDataSource: ParticipationDataSource
|
||||||
|
abstract val friendDataSource: FriendDataSource
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package allin.data
|
||||||
|
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.*
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
interface BetDataSource {
|
||||||
|
fun getAllBets(filters: List<BetFilter>, userDTO: UserDTO): List<Bet>
|
||||||
|
fun getBetById(id: String): Bet?
|
||||||
|
fun getBetDetailById(id: String, userid: String): BetDetail?
|
||||||
|
fun getBetsNotFinished(): List<Bet>
|
||||||
|
fun addBet(bet: Bet)
|
||||||
|
fun removeBet(id: String): Boolean
|
||||||
|
fun updateBet(data: UpdatedBetData): Boolean
|
||||||
|
fun updateBetStatuses(date: ZonedDateTime)
|
||||||
|
fun getToConfirm(user: UserDTO): List<BetDetail>
|
||||||
|
fun confirmBet(betId: String, result: String)
|
||||||
|
fun getWonNotifications(userid: String): List<BetResultDetail>
|
||||||
|
fun getHistory(userid: String): List<BetResultDetail>
|
||||||
|
fun getCurrent(userid: String): List<BetDetail>
|
||||||
|
fun getMostPopularBet(): Bet?
|
||||||
|
fun updatePopularityScore(betId: String)
|
||||||
|
fun addPrivateBet(bet: Bet)
|
||||||
|
fun isInvited(betid: String, userId: String): Boolean
|
||||||
|
fun addUserInPrivatebet(updatedPrivateBet: UpdatedPrivateBet)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package allin.data
|
||||||
|
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.FriendStatus
|
||||||
|
|
||||||
|
interface FriendDataSource {
|
||||||
|
fun addFriend(sender: String, receiver: String)
|
||||||
|
fun getFriendFromUserId(id: String): List<UserDTO>
|
||||||
|
fun getFriendRequestsFromUserId(id: String): List<UserDTO>
|
||||||
|
fun deleteFriend(senderId: String, receiverId: String): Boolean
|
||||||
|
fun isFriend(firstUser: String, secondUser: String): Boolean
|
||||||
|
fun filterUsersByUsername(fromUserId: String, search: String): List<UserDTO>
|
||||||
|
|
||||||
|
fun getFriendStatus(firstUser: String, secondUser: String) =
|
||||||
|
if (isFriend(firstUser, secondUser)) {
|
||||||
|
if (isFriend(secondUser, firstUser)) {
|
||||||
|
FriendStatus.FRIEND
|
||||||
|
} else FriendStatus.REQUESTED
|
||||||
|
} else FriendStatus.NOT_FRIEND
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package allin.data
|
||||||
|
|
||||||
|
import allin.model.Participation
|
||||||
|
|
||||||
|
interface ParticipationDataSource {
|
||||||
|
fun addParticipation(participation: Participation)
|
||||||
|
fun getParticipationFromBetId(betid: String): List<Participation>
|
||||||
|
fun deleteParticipation(id: String): Boolean
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package allin.data
|
||||||
|
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.User
|
||||||
|
|
||||||
|
interface UserDataSource {
|
||||||
|
fun getUserByUsername(username: String): Pair<UserDTO?, String?>
|
||||||
|
fun addUser(user: User)
|
||||||
|
fun deleteUser(username: String): Boolean
|
||||||
|
fun addCoins(username: String, amount: Int)
|
||||||
|
fun removeCoins(username: String, amount: Int)
|
||||||
|
fun userExists(username: String): Boolean
|
||||||
|
fun emailExists(email: String): Boolean
|
||||||
|
fun canHaveDailyGift(username: String): Boolean
|
||||||
|
fun addImage(userid: String, image: ByteArray)
|
||||||
|
fun removeImage(userid: String)
|
||||||
|
fun getImage(userid: String): String?
|
||||||
|
fun getUserById(id: String): UserDTO?
|
||||||
|
}
|
@ -0,0 +1,254 @@
|
|||||||
|
package allin.data.mock
|
||||||
|
|
||||||
|
import allin.data.BetDataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.*
|
||||||
|
import allin.model.BetStatus.*
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
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(filters: List<BetFilter>, userDTO: UserDTO): List<Bet> {
|
||||||
|
return when {
|
||||||
|
filters.isEmpty() -> bets
|
||||||
|
|
||||||
|
filters.size == 1 -> {
|
||||||
|
val filter = filters[0]
|
||||||
|
|
||||||
|
when (filter) {
|
||||||
|
BetFilter.PUBLIC -> bets.filter { !it.isPrivate }
|
||||||
|
BetFilter.INVITATION -> bets.filter { it.isPrivate }
|
||||||
|
BetFilter.FINISHED -> bets.filter { it.status == FINISHED }
|
||||||
|
BetFilter.IN_PROGRESS -> bets.filter {
|
||||||
|
it.status in listOf(IN_PROGRESS, WAITING, CLOSING)
|
||||||
|
}
|
||||||
|
}.map { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
bets.filter { bet ->
|
||||||
|
val public = (BetFilter.PUBLIC in filters) && !bet.isPrivate
|
||||||
|
val invitation = (BetFilter.INVITATION in filters) && bet.isPrivate
|
||||||
|
val finished =
|
||||||
|
(BetFilter.FINISHED in filters) and ((bet.status == FINISHED) or (bet.status == CANCELLED))
|
||||||
|
val inProgress = (BetFilter.IN_PROGRESS in filters) and (bet.status in listOf(
|
||||||
|
IN_PROGRESS,
|
||||||
|
WAITING,
|
||||||
|
CLOSING
|
||||||
|
))
|
||||||
|
|
||||||
|
(public || invitation) && (finished or inProgress)
|
||||||
|
}.map { it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBetById(id: String): Bet? =
|
||||||
|
bets.find { 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 {
|
||||||
|
it.isPrivate = data.isPrivate
|
||||||
|
} != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBetsNotFinished(): List<Bet> =
|
||||||
|
bets.filter { it.endBet >= ZonedDateTime.now() }
|
||||||
|
|
||||||
|
override fun addBet(bet: Bet) {
|
||||||
|
bets += bet
|
||||||
|
betInfos += BetInfo(id = bet.id, totalStakes = 0, totalParticipants = 0)
|
||||||
|
bet.response.forEach {
|
||||||
|
answerInfos += BetAnswerInfo(
|
||||||
|
betId = bet.id,
|
||||||
|
response = it,
|
||||||
|
totalStakes = 0,
|
||||||
|
odds = 1f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateBetStatuses(date: ZonedDateTime) {
|
||||||
|
bets.forEachIndexed { idx, bet ->
|
||||||
|
if (bet.status != CANCELLED && bet.status != FINISHED && date >= bet.endRegistration) {
|
||||||
|
bets[idx] = when {
|
||||||
|
date >= bet.endBet && date.plusWeeks(1) >= bet.endBet -> {
|
||||||
|
cancelBet(bet)
|
||||||
|
}
|
||||||
|
|
||||||
|
date >= bet.endBet -> bet.copy(status = CLOSING)
|
||||||
|
else -> bet.copy(status = WAITING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelBet(bet: Bet): Bet {
|
||||||
|
participations
|
||||||
|
.filter { it.betId == bet.id }
|
||||||
|
.forEach { p ->
|
||||||
|
users.replaceAll { user ->
|
||||||
|
if (user.username == p.username) {
|
||||||
|
user.copy(nbCoins = user.nbCoins + p.stake)
|
||||||
|
} else user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bet.copy(status = CANCELLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getToConfirm(user: UserDTO): List<BetDetail> =
|
||||||
|
bets.filter { it.createdBy == user.id && it.status == CLOSING }
|
||||||
|
.map { it.toBetDetail(user.username) }
|
||||||
|
|
||||||
|
override fun confirmBet(betId: String, result: String) {
|
||||||
|
results.add(
|
||||||
|
BetResult(
|
||||||
|
betId = betId,
|
||||||
|
result = result
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bets.replaceAll {
|
||||||
|
if (it.id == betId) {
|
||||||
|
it.copy(status = FINISHED)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
val resultAnswerInfo = answerInfos.find { it.betId == betId && it.response == result }
|
||||||
|
participations.filter { it.betId == betId && it.answer == result }
|
||||||
|
.forEach { participation ->
|
||||||
|
|
||||||
|
val amount = (participation.stake * (resultAnswerInfo?.odds ?: 1f)).roundToInt()
|
||||||
|
users.replaceAll {
|
||||||
|
if (it.username == participation.username) {
|
||||||
|
it.copy(nbCoins = it.nbCoins + amount)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
resultNotifications.add(Pair(betId, participation.username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWonNotifications(userid: String): List<BetResultDetail> {
|
||||||
|
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.userId == userid && it.betId == bet.id }
|
||||||
|
?: return@map null
|
||||||
|
|
||||||
|
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 * (answerInfo?.odds ?: 1f)).roundToInt(),
|
||||||
|
won = true
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
}.mapNotNull { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHistory(username: String): List<BetResultDetail> {
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = if (won) {
|
||||||
|
(participation.stake * (answerInfo?.odds ?: 1f)).roundToInt()
|
||||||
|
} else participation.stake,
|
||||||
|
won = won
|
||||||
|
)
|
||||||
|
}.mapNotNull { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrent(username: String): List<BetDetail> {
|
||||||
|
return bets.mapNotNull { bet ->
|
||||||
|
when (bet.status) {
|
||||||
|
CANCELLED, FINISHED -> return@mapNotNull null
|
||||||
|
else -> {
|
||||||
|
val userParticipation = participations.find { it.username == username && it.betId == bet.id }
|
||||||
|
if (userParticipation == null) return@mapNotNull null
|
||||||
|
return@mapNotNull bet.toBetDetail(username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
wonParticipation = if (this.status == FINISHED) {
|
||||||
|
val result = results.find { it.betId == this.id }
|
||||||
|
result?.let { r ->
|
||||||
|
participations
|
||||||
|
.filter { it.answer == r.result }
|
||||||
|
.maxBy { it.stake }
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addPrivateBet(bet: Bet) {
|
||||||
|
addBet(bet)
|
||||||
|
bet.userInvited?.forEach {
|
||||||
|
mockData.privatebets.add(InvitationBet(bet.id, it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInvited(betid: String, userId: String) =
|
||||||
|
mockData.privatebets.filter { (it.betid == betid) and (it.userId == userId) }.isNotEmpty()
|
||||||
|
|
||||||
|
|
||||||
|
override fun addUserInPrivatebet(updatedPrivateBet: UpdatedPrivateBet) {
|
||||||
|
updatedPrivateBet.usersInvited?.forEach {
|
||||||
|
mockData.privatebets.add(InvitationBet(updatedPrivateBet.betid, it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package allin.data.mock
|
||||||
|
|
||||||
|
import allin.data.*
|
||||||
|
import allin.model.*
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
class MockDataSource : AllInDataSource() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
println("APP STARTING ON MOCK DATA SOURCE")
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockData {
|
||||||
|
val bets by lazy { mutableListOf<Bet>() }
|
||||||
|
val betInfos by lazy { mutableListOf<BetInfo>() }
|
||||||
|
val answerInfos by lazy { mutableListOf<BetAnswerInfo>() }
|
||||||
|
val results by lazy { mutableListOf<BetResult>() }
|
||||||
|
val resultNotifications by lazy { mutableListOf<Pair<String, String>>() }
|
||||||
|
val users by lazy { mutableListOf<User>() }
|
||||||
|
val lastGifts by lazy { mutableMapOf<String, ZonedDateTime>() }
|
||||||
|
val participations by lazy { mutableListOf<Participation>() }
|
||||||
|
val friends by lazy { mutableListOf<Friend>() }
|
||||||
|
val privatebets by lazy { mutableListOf<InvitationBet>() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mockData by lazy { MockData() }
|
||||||
|
|
||||||
|
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) }
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package allin.data.mock
|
||||||
|
|
||||||
|
import allin.data.FriendDataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.Friend
|
||||||
|
import allin.model.FriendStatus
|
||||||
|
|
||||||
|
class MockFriendDataSource(private val mockData: MockDataSource.MockData) : FriendDataSource {
|
||||||
|
|
||||||
|
private val friends get() = mockData.friends
|
||||||
|
private val users get() = mockData.users
|
||||||
|
|
||||||
|
override fun addFriend(sender: String, receiver: String) {
|
||||||
|
mockData.friends.add(Friend(sender, receiver))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendFromUserId(id: String) =
|
||||||
|
friends.map { Friend(sender = it.sender, receiver = it.receiver) }
|
||||||
|
.filter { it.sender == id }
|
||||||
|
.mapNotNull {
|
||||||
|
users.find { usr -> it.receiver == usr.id }
|
||||||
|
?.toDto(
|
||||||
|
friendStatus = if (isFriend(it.receiver, id)) {
|
||||||
|
FriendStatus.FRIEND
|
||||||
|
} else FriendStatus.REQUESTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendRequestsFromUserId(id: String): List<UserDTO> {
|
||||||
|
return friends
|
||||||
|
.filter { (it.receiver == id) && !isFriend(id, it.sender) }
|
||||||
|
.mapNotNull {
|
||||||
|
users.find { usr -> usr.id == it.sender }
|
||||||
|
?.toDto(friendStatus = FriendStatus.NOT_FRIEND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteFriend(senderId: String, receiverId: String) =
|
||||||
|
friends.removeIf { (it.sender == senderId) && (it.receiver == receiverId) }
|
||||||
|
|
||||||
|
|
||||||
|
override fun isFriend(firstUser: String, secondUser: String) =
|
||||||
|
friends.any { (it.sender == firstUser) and (it.receiver == secondUser) }
|
||||||
|
|
||||||
|
|
||||||
|
override fun filterUsersByUsername(fromUserId: String, search: String): List<UserDTO> =
|
||||||
|
users.filter { (it.username.contains(search, ignoreCase = true)) }
|
||||||
|
.map { user ->
|
||||||
|
user.toDto(friendStatus = getFriendStatus(fromUserId, user.id))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package allin.data.mock
|
||||||
|
|
||||||
|
import allin.data.ParticipationDataSource
|
||||||
|
import allin.model.Participation
|
||||||
|
|
||||||
|
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
|
||||||
|
val betTotalParticipants = it.totalParticipants + 1
|
||||||
|
it.copy(
|
||||||
|
totalStakes = betTotalStakes,
|
||||||
|
totalParticipants = betTotalParticipants
|
||||||
|
)
|
||||||
|
} 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<Participation> =
|
||||||
|
participations.filter { it.betId == betid }
|
||||||
|
|
||||||
|
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
|
||||||
|
val betTotalParticipants = it.totalParticipants - 1
|
||||||
|
it.copy(
|
||||||
|
totalStakes = betTotalStakes,
|
||||||
|
totalParticipants = betTotalParticipants
|
||||||
|
)
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package allin.data.mock
|
||||||
|
|
||||||
|
import allin.data.UserDataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.User
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
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<UserDTO?, String?> =
|
||||||
|
users.find { (it.username == username) or (it.email == username) }?.let { usr ->
|
||||||
|
Pair(
|
||||||
|
UserDTO(
|
||||||
|
id = usr.id,
|
||||||
|
username = usr.username,
|
||||||
|
email = usr.email,
|
||||||
|
nbCoins = usr.nbCoins,
|
||||||
|
token = usr.token,
|
||||||
|
image = null,
|
||||||
|
nbBets = mockData.participations.count { it.userId == usr.id },
|
||||||
|
nbFriends = mockData.friends.count { f ->
|
||||||
|
f.receiver == usr.username &&
|
||||||
|
mockData.friends.any { it.sender == usr.username && it.receiver == f.sender }
|
||||||
|
},
|
||||||
|
bestWin = mockData.participations
|
||||||
|
.filter {
|
||||||
|
(it.id == usr.id) &&
|
||||||
|
(mockData.results.find { r -> r.betId == it.betId })?.result == it.answer
|
||||||
|
}
|
||||||
|
.maxBy { it.stake }
|
||||||
|
.stake,
|
||||||
|
friendStatus = null,
|
||||||
|
),
|
||||||
|
usr.password
|
||||||
|
)
|
||||||
|
} ?: Pair(null, null)
|
||||||
|
|
||||||
|
override fun addUser(user: User) {
|
||||||
|
users += user
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteUser(username: String): Boolean =
|
||||||
|
users.removeIf { (it.username == username) or (it.email == username) }
|
||||||
|
|
||||||
|
override fun addCoins(username: String, amount: Int) {
|
||||||
|
users.find { it.username == username }?.let {
|
||||||
|
it.nbCoins += amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCoins(username: String, amount: Int) {
|
||||||
|
users.find { it.username == username }?.let {
|
||||||
|
it.nbCoins -= amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun userExists(username: String) =
|
||||||
|
users.any { it.username == username }
|
||||||
|
|
||||||
|
override fun emailExists(email: String) =
|
||||||
|
users.any { it.email == email }
|
||||||
|
|
||||||
|
override fun canHaveDailyGift(username: String): Boolean {
|
||||||
|
val value = lastGifts[username]?.let {
|
||||||
|
it.plusDays(1) <= ZonedDateTime.now()
|
||||||
|
} ?: true
|
||||||
|
lastGifts[username] = ZonedDateTime.now()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addImage(userid: String, image: ByteArray) {
|
||||||
|
val user = users.find { it.id == userid }
|
||||||
|
if (user != null) {
|
||||||
|
user.image = image.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeImage(userid: String) {
|
||||||
|
val user = users.find { it.id == userid }
|
||||||
|
if (user != null) {
|
||||||
|
user.image = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImage(userid: String) =
|
||||||
|
users.find { it.id == userid }?.image
|
||||||
|
|
||||||
|
|
||||||
|
override fun getUserById(id: String) =
|
||||||
|
mockData.users.find { it.id == id }?.let { usr ->
|
||||||
|
UserDTO(
|
||||||
|
id = usr.id,
|
||||||
|
username = usr.username,
|
||||||
|
email = usr.email,
|
||||||
|
nbCoins = usr.nbCoins,
|
||||||
|
token = usr.token,
|
||||||
|
image = null,
|
||||||
|
nbBets = mockData.participations.count { it.userId == usr.id },
|
||||||
|
nbFriends = mockData.friends.count { f ->
|
||||||
|
f.receiver == usr.username &&
|
||||||
|
mockData.friends.any { it.sender == usr.username && it.receiver == f.sender }
|
||||||
|
},
|
||||||
|
bestWin = mockData.participations
|
||||||
|
.filter {
|
||||||
|
(it.id == usr.id) &&
|
||||||
|
(mockData.results.find { r -> r.betId == it.betId })?.result == it.answer
|
||||||
|
}
|
||||||
|
.maxBy { it.stake }
|
||||||
|
.stake,
|
||||||
|
friendStatus = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,296 @@
|
|||||||
|
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.*
|
||||||
|
import org.ktorm.entity.*
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class PostgresBetDataSource(private val database: Database) : BetDataSource {
|
||||||
|
|
||||||
|
override fun getAllBets(filters: List<BetFilter>, userDTO: UserDTO): List<Bet> {
|
||||||
|
return when {
|
||||||
|
filters.isEmpty() -> database.bets.map { it.toBet(database) }
|
||||||
|
.filter { (!it.isPrivate) or (isInvited(it.id, userDTO.id)) or (it.createdBy == userDTO.username) }
|
||||||
|
|
||||||
|
filters.size == 1 -> {
|
||||||
|
val filter = filters.first()
|
||||||
|
when (filter) {
|
||||||
|
BetFilter.PUBLIC -> database.bets.filter { !it.isPrivate }
|
||||||
|
BetFilter.INVITATION -> database.bets.filter { it.isPrivate }
|
||||||
|
BetFilter.FINISHED -> database.bets.filter { it.status eq BetStatus.FINISHED }
|
||||||
|
BetFilter.IN_PROGRESS -> database.bets.filter {
|
||||||
|
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 -> {
|
||||||
|
database.bets.filter { bet ->
|
||||||
|
val public = (BetFilter.PUBLIC in filters) and !bet.isPrivate
|
||||||
|
val invitation = (BetFilter.INVITATION in filters) and bet.isPrivate
|
||||||
|
val finished =
|
||||||
|
(BetFilter.FINISHED in filters) and ((bet.status eq BetStatus.FINISHED) or (bet.status eq BetStatus.CANCELLED))
|
||||||
|
val inProgress = (BetFilter.IN_PROGRESS in filters) and (bet.status inList listOf(
|
||||||
|
BetStatus.IN_PROGRESS,
|
||||||
|
BetStatus.WAITING,
|
||||||
|
BetStatus.CLOSING
|
||||||
|
))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
|
||||||
|
override fun getBetDetailById(id: String, userid: String): BetDetail? =
|
||||||
|
database.bets.find { it.id eq id }?.toBetDetail(database, userid)
|
||||||
|
|
||||||
|
override fun getBetsNotFinished(): List<Bet> {
|
||||||
|
val currentTime = ZonedDateTime.now(ZoneId.of("+02:00"))
|
||||||
|
return database.bets
|
||||||
|
.filter { it.endBet greaterEq currentTime.toInstant() }
|
||||||
|
.map { it.toBet(database) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getToConfirm(user: UserDTO): List<BetDetail> {
|
||||||
|
return database.bets
|
||||||
|
.filter {
|
||||||
|
(it.createdBy eq user.id) and (BetsEntity.status eq BetStatus.CLOSING)
|
||||||
|
}
|
||||||
|
.map { it.toBetDetail(database, user.id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun confirmBet(betId: String, result: String) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resultAnswerInfo = database.betAnswerInfos
|
||||||
|
.find { (it.betId eq betId) and (it.response eq result) }
|
||||||
|
?.toBetAnswerInfo()
|
||||||
|
|
||||||
|
database.participations.filter {
|
||||||
|
(ParticipationsEntity.betId eq betId) and
|
||||||
|
(ParticipationsEntity.answer eq result)
|
||||||
|
}.forEach { participation ->
|
||||||
|
database.betResultNotifications.add(
|
||||||
|
BetResultNotificationEntity {
|
||||||
|
this.betId = betId
|
||||||
|
this.userid = participation.userid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val amount = (participation.stake * (resultAnswerInfo?.odds ?: 1f)).roundToInt()
|
||||||
|
database.update(UsersEntity) { usr ->
|
||||||
|
set(usr.nbCoins, usr.nbCoins + amount)
|
||||||
|
where { usr.id eq participation.userid }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWonNotifications(userid: String): List<BetResultDetail> {
|
||||||
|
return database.betResultNotifications
|
||||||
|
.filter { it.userid eq userid }
|
||||||
|
.flatMap { notif ->
|
||||||
|
notif.delete()
|
||||||
|
|
||||||
|
database.participations
|
||||||
|
.filter {
|
||||||
|
(it.userid eq userid) and
|
||||||
|
(it.betId eq notif.betId)
|
||||||
|
}
|
||||||
|
.mapNotNull { participation ->
|
||||||
|
database.betResults
|
||||||
|
.find { it.betId eq participation.bet.id }
|
||||||
|
?.toBetResultDetail(
|
||||||
|
database,
|
||||||
|
participation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHistory(userid: String): List<BetResultDetail> {
|
||||||
|
return database.participations
|
||||||
|
.filter { it.userid eq userid }
|
||||||
|
.mapNotNull { participation ->
|
||||||
|
database.betResults
|
||||||
|
.find { it.betId eq participation.bet.id }
|
||||||
|
?.toBetResultDetail(
|
||||||
|
database,
|
||||||
|
participation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrent(userid: String): List<BetDetail> {
|
||||||
|
return database.participations
|
||||||
|
.filter { it.userid eq userid }
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.bet.status !in listOf(BetStatus.FINISHED, BetStatus.CANCELLED)) {
|
||||||
|
it.bet.toBetDetail(
|
||||||
|
database = database,
|
||||||
|
userid = userid
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMostPopularBet(): Bet? {
|
||||||
|
return database.bets
|
||||||
|
.filter { (it.isPrivate eq false) and (it.status eq BetStatus.IN_PROGRESS) }
|
||||||
|
.sortedByDescending { it.popularityscore }
|
||||||
|
.firstOrNull()
|
||||||
|
?.toBet(database)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
database.betInfos.add(
|
||||||
|
BetInfoEntity {
|
||||||
|
this.id = bet.id
|
||||||
|
this.totalStakes = 0
|
||||||
|
this.totalParticipants = 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val responses = if (bet.type == BetType.BINARY) {
|
||||||
|
listOf(YES_VALUE, NO_VALUE)
|
||||||
|
} else {
|
||||||
|
bet.response
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.forEach { response ->
|
||||||
|
database.betAnswerInfos.add(
|
||||||
|
BetAnswerInfoEntity {
|
||||||
|
this.betId = bet.id
|
||||||
|
this.response = response
|
||||||
|
this.totalStakes = 0
|
||||||
|
this.odds = 1f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (bet.type == BetType.CUSTOM) {
|
||||||
|
database.responses.add(
|
||||||
|
ResponseEntity {
|
||||||
|
this.betId = bet.id
|
||||||
|
this.response = response
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeBet(id: String): Boolean {
|
||||||
|
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 {
|
||||||
|
return database.update(BetsEntity) {
|
||||||
|
set(BetsEntity.isPrivate, data.isPrivate)
|
||||||
|
where { BetsEntity.id eq data.id }
|
||||||
|
} > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateBetStatuses(date: ZonedDateTime) {
|
||||||
|
database.bets
|
||||||
|
.filter {
|
||||||
|
(date.toInstant() greaterEq BetsEntity.endRegistration) and
|
||||||
|
(BetsEntity.status notEq BetStatus.FINISHED) and
|
||||||
|
(BetsEntity.status notEq BetStatus.CANCELLED)
|
||||||
|
}.let {
|
||||||
|
it.filter { date.toInstant() less BetsEntity.endBet }.forEach { bet ->
|
||||||
|
bet.status = BetStatus.WAITING
|
||||||
|
bet.flushChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.filter { date.toInstant() greaterEq BetsEntity.endBet }.forEach { bet ->
|
||||||
|
if (date.toInstant() >= bet.endBet.plus(7, ChronoUnit.DAYS)) {
|
||||||
|
database.participations
|
||||||
|
.filter { it.betId eq bet.id }
|
||||||
|
.forEach { participation ->
|
||||||
|
database.users.find { it.id eq participation.userid }?.let { user ->
|
||||||
|
user.nbCoins += participation.stake
|
||||||
|
user.flushChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bet.status = BetStatus.CANCELLED
|
||||||
|
bet.flushChanges()
|
||||||
|
} else {
|
||||||
|
bet.status = BetStatus.CLOSING
|
||||||
|
bet.flushChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) =
|
||||||
|
database.privatebets.filter { (it.betid eq betid) and (it.userId eq userId) }.isNotEmpty()
|
||||||
|
|
||||||
|
|
||||||
|
override fun addUserInPrivatebet(updatedPrivateBet: UpdatedPrivateBet) {
|
||||||
|
updatedPrivateBet.usersInvited?.forEach {
|
||||||
|
database.privatebets.add(PrivateBetEntity {
|
||||||
|
betId = updatedPrivateBet.betid
|
||||||
|
userId = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
package allin.data.postgres
|
||||||
|
|
||||||
|
import allin.data.*
|
||||||
|
import allin.ext.execute
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.support.postgresql.PostgreSqlDialect
|
||||||
|
|
||||||
|
class PostgresDataSource : AllInDataSource() {
|
||||||
|
|
||||||
|
private val database: Database
|
||||||
|
|
||||||
|
init {
|
||||||
|
val dbDatabase = System.getenv()["POSTGRES_DB"]
|
||||||
|
val dbUser = System.getenv()["POSTGRES_USER"]
|
||||||
|
val dbPassword = System.getenv()["POSTGRES_PASSWORD"]
|
||||||
|
val dbHost = System.getenv()["POSTGRES_HOST"]
|
||||||
|
val url = "jdbc:postgresql://$dbHost/$dbDatabase"
|
||||||
|
|
||||||
|
println("APP STARTING ON POSTGRESQL DATA SOURCE $url")
|
||||||
|
|
||||||
|
database = Database.connect(
|
||||||
|
url = url,
|
||||||
|
user = dbUser,
|
||||||
|
password = dbPassword,
|
||||||
|
dialect = PostgreSqlDialect()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE EXTENSION IF not exists fuzzystrmatch;
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF not exists users (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
username VARCHAR(255),
|
||||||
|
password VARCHAR(255),
|
||||||
|
coins int,
|
||||||
|
email VARCHAR(255),
|
||||||
|
lastgift timestamp
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF not exists bet (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
theme VARCHAR(255),
|
||||||
|
endregistration timestamp,
|
||||||
|
endbet timestamp,
|
||||||
|
zoneid varchar(500),
|
||||||
|
sentencebet varchar(500),
|
||||||
|
isprivate boolean,
|
||||||
|
createdby varchar(250),
|
||||||
|
status varchar(20),
|
||||||
|
type varchar(20),
|
||||||
|
popularityscore numeric
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS betresult (
|
||||||
|
betid VARCHAR(255) PRIMARY KEY REFERENCES bet,
|
||||||
|
result varchar(250)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS betresultnotification (
|
||||||
|
betid VARCHAR(255),
|
||||||
|
userid varchar(250),
|
||||||
|
CONSTRAINT pk_id_username PRIMARY KEY (betid, userid)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS participation (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
bet VARCHAR(255),
|
||||||
|
userid varchar(250),
|
||||||
|
answer varchar(250),
|
||||||
|
stake int
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS response (
|
||||||
|
betId VARCHAR(255),
|
||||||
|
response VARCHAR(250),
|
||||||
|
CONSTRAINT pk_response_id PRIMARY KEY (betId, response)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF not exists betInfo (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
totalStakes int,
|
||||||
|
totalParticipants 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()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS friend(
|
||||||
|
sender VARCHAR(255),
|
||||||
|
receiver VARCHAR(255),
|
||||||
|
CONSTRAINT pk_friend PRIMARY KEY (sender,receiver)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
database.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS userimage
|
||||||
|
(
|
||||||
|
user_id VARCHAR(255) PRIMARY KEY,
|
||||||
|
image bytea
|
||||||
|
)
|
||||||
|
""".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) }
|
||||||
|
override val betDataSource: BetDataSource by lazy { PostgresBetDataSource(database) }
|
||||||
|
override val participationDataSource: ParticipationDataSource by lazy { PostgresParticipationDataSource(database) }
|
||||||
|
override val friendDataSource: FriendDataSource by lazy { PostgresFriendDataSource(database) }
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package allin.data.postgres
|
||||||
|
|
||||||
|
import allin.data.FriendDataSource
|
||||||
|
import allin.data.postgres.entities.FriendEntity
|
||||||
|
import allin.data.postgres.entities.friends
|
||||||
|
import allin.data.postgres.entities.users
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.ext.length
|
||||||
|
import allin.ext.levenshteinLessEq
|
||||||
|
import allin.ext.toLowerCase
|
||||||
|
import allin.model.FriendStatus
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.and
|
||||||
|
import org.ktorm.dsl.div
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.dsl.notEq
|
||||||
|
import org.ktorm.entity.*
|
||||||
|
|
||||||
|
class PostgresFriendDataSource(private val database: Database) : FriendDataSource {
|
||||||
|
override fun addFriend(sender: String, receiver: String) {
|
||||||
|
database.friends.add(
|
||||||
|
FriendEntity {
|
||||||
|
this.sender = sender
|
||||||
|
this.receiver = receiver
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFriendFromUserId(id: String): List<UserDTO> {
|
||||||
|
return database.friends.map { it.toFriend() }
|
||||||
|
.filter { it.sender == id }
|
||||||
|
.mapNotNull { friend ->
|
||||||
|
val receiverUser = database.users.find { usr ->
|
||||||
|
usr.id eq friend.receiver
|
||||||
|
}
|
||||||
|
receiverUser?.toUserDTO(
|
||||||
|
database,
|
||||||
|
friendStatus = if (isFriend(friend.receiver, id)) {
|
||||||
|
FriendStatus.FRIEND
|
||||||
|
} else FriendStatus.REQUESTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getFriendRequestsFromUserId(id: String): List<UserDTO> {
|
||||||
|
return database.friends
|
||||||
|
.filter { it.receiver eq id }
|
||||||
|
.mapNotNull {
|
||||||
|
if (isFriend(firstUser = id, secondUser = it.sender)) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
database.users.find { usr ->
|
||||||
|
usr.id eq it.sender
|
||||||
|
}?.toUserDTO(database, friendStatus = FriendStatus.NOT_FRIEND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun deleteFriend(senderId: String, receiverId: String): Boolean {
|
||||||
|
val result = database.friends.removeIf { (it.sender eq receiverId) and (it.receiver eq senderId) } +
|
||||||
|
database.friends.removeIf { (it.sender eq senderId) and (it.receiver eq receiverId) }
|
||||||
|
|
||||||
|
return result > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isFriend(firstUser: String, secondUser: String) =
|
||||||
|
database.friends.any { (it.sender eq firstUser) and (it.receiver eq secondUser) }
|
||||||
|
|
||||||
|
override fun filterUsersByUsername(fromUserId: String, search: String): List<UserDTO> {
|
||||||
|
return database.users
|
||||||
|
.filter { (it.id notEq fromUserId) }
|
||||||
|
.mapColumns {
|
||||||
|
tupleOf(
|
||||||
|
it.id,
|
||||||
|
it.username,
|
||||||
|
it.username.toLowerCase().levenshteinLessEq(
|
||||||
|
search.lowercase(),
|
||||||
|
(it.username.length() / 2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.filter { (_, username, distance) ->
|
||||||
|
val maxSize = ((username?.length ?: 0) / 2)
|
||||||
|
distance?.let { it <= maxSize } ?: false
|
||||||
|
}
|
||||||
|
.sortedBy { it.second }
|
||||||
|
.mapNotNull { (id, _, _) ->
|
||||||
|
id?.let {
|
||||||
|
val user = database.users.find { it.id eq id }
|
||||||
|
user?.toUserDTO(database, friendStatus = getFriendStatus(fromUserId, user.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package allin.data.postgres
|
||||||
|
|
||||||
|
import allin.data.ParticipationDataSource
|
||||||
|
import allin.data.postgres.entities.*
|
||||||
|
import allin.model.Participation
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.dsl.insert
|
||||||
|
import org.ktorm.entity.*
|
||||||
|
|
||||||
|
class PostgresParticipationDataSource(private val database: Database) : ParticipationDataSource {
|
||||||
|
|
||||||
|
override fun addParticipation(participation: Participation) {
|
||||||
|
database.insert(ParticipationsEntity) {
|
||||||
|
set(it.id, participation.id)
|
||||||
|
set(it.betId, participation.betId)
|
||||||
|
set(it.userid, participation.userId)
|
||||||
|
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
|
||||||
|
this.totalParticipants = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
betInfo.totalStakes += participation.stake
|
||||||
|
betInfo.totalParticipants++
|
||||||
|
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 = if (probability == 0f) 1f else 1 / probability
|
||||||
|
it.flushChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getParticipationFromBetId(betid: String): List<Participation> =
|
||||||
|
database.participations.filter { it.betId eq betid }.map { it.toParticipation(database) }
|
||||||
|
|
||||||
|
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
|
||||||
|
betInfo.totalParticipants--
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
|||||||
|
package allin.data.postgres
|
||||||
|
|
||||||
|
import allin.data.UserDataSource
|
||||||
|
import allin.data.postgres.entities.*
|
||||||
|
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<UserDTO?, String?> =
|
||||||
|
database.users
|
||||||
|
.find { (it.username eq username) or (it.email eq username) }
|
||||||
|
?.let { it.toUserDTO(database) to it.password }
|
||||||
|
?: (null to null)
|
||||||
|
|
||||||
|
override fun getUserById(id: String): UserDTO? =
|
||||||
|
database.users.find { it.id eq id }?.toUserDTO(database)
|
||||||
|
|
||||||
|
override fun addUser(user: User) {
|
||||||
|
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 =
|
||||||
|
database.users.removeIf { (it.username eq username) or (it.email eq username) } > 0
|
||||||
|
|
||||||
|
override fun addCoins(username: String, amount: Int) {
|
||||||
|
database.update(UsersEntity) {
|
||||||
|
set(it.nbCoins, it.nbCoins + amount)
|
||||||
|
where { it.username eq username }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCoins(username: String, amount: Int) {
|
||||||
|
database.update(UsersEntity) {
|
||||||
|
set(it.nbCoins, it.nbCoins - amount)
|
||||||
|
where { it.username eq username }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun userExists(username: String) =
|
||||||
|
database.users.filter {
|
||||||
|
(it.username eq username)
|
||||||
|
}.totalRecords > 0
|
||||||
|
|
||||||
|
override fun emailExists(email: String) =
|
||||||
|
database.users.filter {
|
||||||
|
(it.email eq email)
|
||||||
|
}.totalRecords > 0
|
||||||
|
|
||||||
|
override fun canHaveDailyGift(username: String): Boolean {
|
||||||
|
val request =
|
||||||
|
"SELECT CASE WHEN DATE(NOW()) > DATE(lastgift) THEN true ELSE false END AS is_lastgift_greater_than_1_day FROM users WHERE username = '$username';"
|
||||||
|
val resultSet = database.executeWithResult(request)
|
||||||
|
|
||||||
|
resultSet?.use {
|
||||||
|
if (resultSet.next()) {
|
||||||
|
val isDailyGift = resultSet.getBoolean("is_lastgift_greater_than_1_day")
|
||||||
|
if (isDailyGift) {
|
||||||
|
database.update(UsersEntity) {
|
||||||
|
set(UsersEntity.lastGift, now())
|
||||||
|
where { it.username eq username }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDailyGift
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addImage(userid: String, image: ByteArray) {
|
||||||
|
database.usersimage.add(UserImageEntity {
|
||||||
|
id = userid
|
||||||
|
this.image = image
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeImage(userid: String) {
|
||||||
|
database.usersimage.removeIf { it.id eq userid }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImage(userid: String): String? {
|
||||||
|
val resultSet =
|
||||||
|
database.executeWithResult(
|
||||||
|
"""
|
||||||
|
SELECT encode(image, 'base64') AS image
|
||||||
|
FROM userimage
|
||||||
|
WHERE user_id = '${userid}'
|
||||||
|
""".trimIndent()
|
||||||
|
) ?: return null
|
||||||
|
if (resultSet.next()) {
|
||||||
|
resultSet.getString("image")?.let { return it }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -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<BetAnswerInfoEntity> {
|
||||||
|
companion object : Entity.Factory<BetAnswerInfoEntity>()
|
||||||
|
|
||||||
|
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<BetAnswerInfoEntity>("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)
|
@ -0,0 +1,98 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
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<BetEntity> {
|
||||||
|
companion object : Entity.Factory<BetEntity>()
|
||||||
|
|
||||||
|
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
|
||||||
|
var popularityscore: Int
|
||||||
|
|
||||||
|
fun toBet(database: Database): Bet {
|
||||||
|
val betInfo = database.betInfos.find { it.id eq this.id }
|
||||||
|
return 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 = database.users.first { it.id eq createdBy }.username,
|
||||||
|
popularityscore = popularityscore,
|
||||||
|
totalStakes = betInfo?.totalStakes ?: 0,
|
||||||
|
totalParticipants = betInfo?.totalParticipants ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toBetDetail(database: Database, userid: String): BetDetail {
|
||||||
|
val bet = this.toBet(database)
|
||||||
|
val participations = database.participations.filter { it.betId eq bet.id }
|
||||||
|
val userParticipation = participations.find { it.userid eq userid }
|
||||||
|
val participationEntities = participations.map { it.toParticipation(database) }
|
||||||
|
|
||||||
|
val answerInfos = database.betAnswerInfos
|
||||||
|
.filter { it.betId eq bet.id }
|
||||||
|
.map { it.toBetAnswerInfo() }
|
||||||
|
|
||||||
|
val betD = BetDetail(
|
||||||
|
bet = bet,
|
||||||
|
answers = getBetAnswerDetail(bet, participationEntities, answerInfos),
|
||||||
|
participations = participationEntities,
|
||||||
|
userParticipation = userParticipation?.toParticipation(database),
|
||||||
|
wonParticipation = if (participationEntities.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else if (bet.status == BetStatus.FINISHED) {
|
||||||
|
database.betResults.find { it.betId eq this.id }?.let { r ->
|
||||||
|
participationEntities
|
||||||
|
.filter { it.answer == r.result }
|
||||||
|
.maxBy { it.stake }
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return betD
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object BetsEntity : Table<BetEntity>("bet") {
|
||||||
|
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").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<BetStatus>("status").bindTo { it.status }
|
||||||
|
val type = enum<BetType>("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)
|
@ -0,0 +1,25 @@
|
|||||||
|
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<BetInfoEntity> {
|
||||||
|
companion object : Entity.Factory<BetInfoEntity>()
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var totalStakes: Int
|
||||||
|
var totalParticipants: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
object BetInfosEntity : Table<BetInfoEntity>("betinfo") {
|
||||||
|
val id = varchar("id").primaryKey().bindTo { it.id }
|
||||||
|
val totalStakes = int("totalstakes").bindTo { it.totalStakes }
|
||||||
|
val totalParticipants = int("totalparticipants").bindTo { it.totalParticipants }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.betInfos get() = this.sequenceOf(BetInfosEntity)
|
@ -0,0 +1,51 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
import allin.model.BetResult
|
||||||
|
import allin.model.BetResultDetail
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.and
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.entity.Entity
|
||||||
|
import org.ktorm.entity.find
|
||||||
|
import org.ktorm.entity.sequenceOf
|
||||||
|
import org.ktorm.schema.Table
|
||||||
|
import org.ktorm.schema.varchar
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
|
interface BetResultEntity : Entity<BetResultEntity> {
|
||||||
|
companion object : Entity.Factory<BetResultEntity>()
|
||||||
|
|
||||||
|
var bet: BetEntity
|
||||||
|
var result: String
|
||||||
|
|
||||||
|
fun toBetResult() =
|
||||||
|
BetResult(
|
||||||
|
betId = bet.id,
|
||||||
|
result = result
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toBetResultDetail(
|
||||||
|
database: Database,
|
||||||
|
participationEntity: ParticipationEntity
|
||||||
|
): BetResultDetail {
|
||||||
|
val answerInfo = database.betAnswerInfos.find {
|
||||||
|
(it.betId eq bet.id) and (it.response eq participationEntity.answer)
|
||||||
|
}?.toBetAnswerInfo()
|
||||||
|
|
||||||
|
return BetResultDetail(
|
||||||
|
betResult = this.toBetResult(),
|
||||||
|
bet = bet.toBet(database),
|
||||||
|
participation = participationEntity.toParticipation(database),
|
||||||
|
amount = (participationEntity.stake * (answerInfo?.odds ?: 1f)).roundToInt(),
|
||||||
|
won = participationEntity.answer == result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BetResultsEntity : Table<BetResultEntity>("betresult") {
|
||||||
|
val betId = varchar("betid").primaryKey().references(BetsEntity) { it.bet }
|
||||||
|
val result = varchar("result").bindTo { it.result }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.betResults get() = this.sequenceOf(BetResultsEntity)
|
@ -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<BetResultNotificationEntity> {
|
||||||
|
companion object : Entity.Factory<BetResultNotificationEntity>()
|
||||||
|
|
||||||
|
var betId: String
|
||||||
|
var userid: String
|
||||||
|
}
|
||||||
|
|
||||||
|
object BetResultNotificationsEntity : Table<BetResultNotificationEntity>("betresultnotification") {
|
||||||
|
val betId = varchar("betid").primaryKey().bindTo { it.betId }
|
||||||
|
val userid = varchar("userid").primaryKey().bindTo { it.userid }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.betResultNotifications get() = this.sequenceOf(BetResultNotificationsEntity)
|
@ -0,0 +1,28 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
import allin.model.Friend
|
||||||
|
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 FriendEntity : Entity<FriendEntity> {
|
||||||
|
companion object : Entity.Factory<FriendEntity>()
|
||||||
|
|
||||||
|
var sender: String
|
||||||
|
var receiver: String
|
||||||
|
|
||||||
|
fun toFriend() =
|
||||||
|
Friend(
|
||||||
|
sender = sender,
|
||||||
|
receiver = receiver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object FriendsEntity : Table<FriendEntity>("friend") {
|
||||||
|
val sender = varchar("sender").primaryKey().bindTo { it.sender }
|
||||||
|
val receiver = varchar("receiver").primaryKey().bindTo { it.receiver }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.friends get() = this.sequenceOf(FriendsEntity)
|
@ -0,0 +1,43 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
import allin.model.Participation
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.entity.Entity
|
||||||
|
import org.ktorm.entity.first
|
||||||
|
import org.ktorm.entity.sequenceOf
|
||||||
|
import org.ktorm.schema.Table
|
||||||
|
import org.ktorm.schema.int
|
||||||
|
import org.ktorm.schema.varchar
|
||||||
|
|
||||||
|
interface ParticipationEntity : Entity<ParticipationEntity> {
|
||||||
|
companion object : Entity.Factory<ParticipationEntity>()
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var bet: BetEntity
|
||||||
|
var userid: String
|
||||||
|
var answer: String
|
||||||
|
var stake: Int
|
||||||
|
|
||||||
|
fun toParticipation(database: Database) =
|
||||||
|
Participation(
|
||||||
|
id = id,
|
||||||
|
betId = bet.id,
|
||||||
|
userId = userid,
|
||||||
|
answer = answer,
|
||||||
|
stake = stake,
|
||||||
|
username = database.users.first { it.id eq userid }.username,
|
||||||
|
imageUser = AppConfig.imageManager.getImage(id, database)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ParticipationsEntity : Table<ParticipationEntity>("participation") {
|
||||||
|
val id = varchar("id").primaryKey().bindTo { it.id }
|
||||||
|
val betId = varchar("bet").references(BetsEntity) { it.bet }
|
||||||
|
val userid = varchar("userid").bindTo { it.userid }
|
||||||
|
val answer = varchar("answer").bindTo { it.answer }
|
||||||
|
val stake = int("stake").bindTo { it.stake }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.participations get() = this.sequenceOf(ParticipationsEntity)
|
@ -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<PrivateBetEntity> {
|
||||||
|
companion object : Entity.Factory<PrivateBetEntity>()
|
||||||
|
|
||||||
|
var betId: String
|
||||||
|
var userId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
object PrivateBetsEntity : Table<PrivateBetEntity>("privatebet") {
|
||||||
|
val betid = varchar("betid").bindTo { it.betId }
|
||||||
|
val userId = varchar("userid").bindTo { it.userId }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.privatebets get() = this.sequenceOf(PrivateBetsEntity)
|
@ -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 ResponseEntity : Entity<ResponseEntity> {
|
||||||
|
companion object : Entity.Factory<ResponseEntity>()
|
||||||
|
|
||||||
|
var betId: String
|
||||||
|
var response: String
|
||||||
|
}
|
||||||
|
|
||||||
|
object ResponsesEntity : Table<ResponseEntity>("response") {
|
||||||
|
val betId = varchar("betid").primaryKey().bindTo { it.betId }
|
||||||
|
val response = varchar("response").primaryKey().bindTo { it.response }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.responses get() = this.sequenceOf(ResponsesEntity)
|
@ -0,0 +1,62 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.model.FriendStatus
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.and
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.entity.*
|
||||||
|
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<UserEntity> {
|
||||||
|
companion object : Entity.Factory<UserEntity>()
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var username: String
|
||||||
|
var email: String
|
||||||
|
var password: String
|
||||||
|
var nbCoins: Int
|
||||||
|
var lastGift: Instant
|
||||||
|
|
||||||
|
fun toUserDTO(database: Database, friendStatus: FriendStatus? = null) =
|
||||||
|
UserDTO(
|
||||||
|
id = id,
|
||||||
|
username = username,
|
||||||
|
email = email,
|
||||||
|
nbCoins = nbCoins,
|
||||||
|
token = null,
|
||||||
|
image = AppConfig.imageManager.getImage(id, database),
|
||||||
|
nbBets = database.participations.count { it.userid eq this.id },
|
||||||
|
nbFriends = database.friends
|
||||||
|
.filter { it.receiver eq this.id }
|
||||||
|
.mapNotNull { p -> database.friends.any { (it.sender eq this.id) and (it.receiver eq p.sender) } }
|
||||||
|
.count(),
|
||||||
|
bestWin = database.participations
|
||||||
|
.filter { (it.id eq this.id) }
|
||||||
|
.mapNotNull { p ->
|
||||||
|
if (database.betResults.any { (it.betId eq p.bet.id) and (it.result eq p.answer) }) {
|
||||||
|
p.stake
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
.maxOrNull() ?: 0,
|
||||||
|
friendStatus = friendStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object UsersEntity : Table<UserEntity>("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").bindTo { it.lastGift }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.users get() = this.sequenceOf(UsersEntity)
|
||||||
|
|
@ -0,0 +1,29 @@
|
|||||||
|
package allin.data.postgres.entities
|
||||||
|
|
||||||
|
import allin.model.UserImage
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.entity.Entity
|
||||||
|
import org.ktorm.entity.sequenceOf
|
||||||
|
import org.ktorm.schema.Table
|
||||||
|
import org.ktorm.schema.bytes
|
||||||
|
import org.ktorm.schema.varchar
|
||||||
|
|
||||||
|
interface UserImageEntity : Entity<UserImageEntity> {
|
||||||
|
companion object : Entity.Factory<UserImageEntity>()
|
||||||
|
|
||||||
|
var id: String
|
||||||
|
var image: ByteArray
|
||||||
|
|
||||||
|
fun toUserImage() =
|
||||||
|
UserImage(
|
||||||
|
id = id,
|
||||||
|
image = image,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object UsersImageEntity : Table<UserImageEntity>("userimage") {
|
||||||
|
val id = varchar("user_id").primaryKey().bindTo { it.id }
|
||||||
|
val image = bytes("image").bindTo { it.image }
|
||||||
|
}
|
||||||
|
|
||||||
|
val Database.usersimage get() = this.sequenceOf(UsersImageEntity)
|
@ -1,4 +1,18 @@
|
|||||||
package allin.dto
|
package allin.dto
|
||||||
|
|
||||||
|
import allin.model.FriendStatus
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserDTO(val id: String, val username: String, val email: String, val nbCoins: Int, var token:String?)
|
data class UserDTO(
|
||||||
|
val id: String,
|
||||||
|
val username: String,
|
||||||
|
val email: String,
|
||||||
|
val nbCoins: Int,
|
||||||
|
var token: String?,
|
||||||
|
val image: String?,
|
||||||
|
var nbBets: Int,
|
||||||
|
var nbFriends: Int,
|
||||||
|
var bestWin: Int,
|
||||||
|
var friendStatus: FriendStatus?
|
||||||
|
)
|
@ -1,65 +0,0 @@
|
|||||||
package allin.entities
|
|
||||||
|
|
||||||
import allin.database
|
|
||||||
import allin.entities.ResponsesEntity.getResponse
|
|
||||||
import allin.model.Bet
|
|
||||||
import allin.utils.Execute
|
|
||||||
import org.ktorm.dsl.*
|
|
||||||
import org.ktorm.entity.Entity
|
|
||||||
import org.ktorm.schema.*
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.util.UUID.fromString
|
|
||||||
|
|
||||||
|
|
||||||
interface BetEntity : Entity<BetEntity> {
|
|
||||||
val theme: String
|
|
||||||
val sentenceBet: String
|
|
||||||
val endRegistration: ZonedDateTime
|
|
||||||
val endBet: ZonedDateTime
|
|
||||||
val isPrivate: Boolean
|
|
||||||
val createdBy: String
|
|
||||||
}
|
|
||||||
|
|
||||||
object BetsEntity : Table<BetEntity>("bet") {
|
|
||||||
val id = uuid("id").primaryKey()
|
|
||||||
val theme = varchar("theme")
|
|
||||||
val sentenceBet = varchar("sentencebet")
|
|
||||||
val endRegistration = timestamp("endregistration")
|
|
||||||
val endBet = timestamp("endbet")
|
|
||||||
val isPrivate = boolean("isprivate")
|
|
||||||
val createdBy = varchar("createdby")
|
|
||||||
|
|
||||||
fun getBets(): MutableList<Bet> {
|
|
||||||
return database.from(BetsEntity).select().map {
|
|
||||||
row -> Bet(
|
|
||||||
row[id].toString(),
|
|
||||||
row[theme].toString(),
|
|
||||||
row[sentenceBet].toString(),
|
|
||||||
row[endRegistration]!!.atZone(ZoneId.of("Europe/Paris")),
|
|
||||||
row[endBet]!!.atZone(ZoneId.of("Europe/Paris")),
|
|
||||||
row[isPrivate]?: false,
|
|
||||||
getResponse(fromString(row[id].toString())),
|
|
||||||
row[createdBy].toString()
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createBetsTable(){
|
|
||||||
val request="CREATE TABLE IF not exists bet ( id uuid PRIMARY KEY, theme VARCHAR(255), endregistration timestamp,endbet timestamp,sentencebet varchar(500),isprivate boolean, createdby varchar(250))"
|
|
||||||
database.Execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addBetEntity(bet : Bet) {
|
|
||||||
database.insert(BetsEntity) {
|
|
||||||
set(it.id, fromString(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)
|
|
||||||
}
|
|
||||||
ResponsesEntity.addResponse(bet.response,fromString(bet.id))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package allin.entities
|
|
||||||
|
|
||||||
import allin.database
|
|
||||||
import allin.model.Participation
|
|
||||||
import allin.utils.Execute
|
|
||||||
import org.ktorm.dsl.*
|
|
||||||
import org.ktorm.entity.Entity
|
|
||||||
import org.ktorm.schema.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
interface ParticipationEntity : Entity<ParticipationEntity> {
|
|
||||||
val id: String
|
|
||||||
val betId: String
|
|
||||||
val username: String
|
|
||||||
val answer: String
|
|
||||||
val stake: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object ParticipationsEntity : Table<BetEntity>("participation") {
|
|
||||||
val id = uuid("id").primaryKey()
|
|
||||||
val betId = uuid("bet")
|
|
||||||
val username = varchar("username")
|
|
||||||
val answer = varchar("answer")
|
|
||||||
val stake = int("stake")
|
|
||||||
|
|
||||||
fun createParticipationTable(){
|
|
||||||
val request="CREATE TABLE IF NOT EXISTS participation (id uuid PRIMARY KEY,bet uuid,username varchar(250),answer varchar(250),stake int);"
|
|
||||||
database.Execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addParticipationEntity(participation : Participation){
|
|
||||||
database.insert(ParticipationsEntity){
|
|
||||||
set(it.id, UUID.fromString(participation.id))
|
|
||||||
set(it.betId,UUID.fromString(participation.betId))
|
|
||||||
set(it.username,participation.username)
|
|
||||||
set(it.answer,participation.answer)
|
|
||||||
set(it.stake,participation.stake)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getParticipationEntityFromBetId(betid: String): MutableList<Participation> {
|
|
||||||
return database.from(ParticipationsEntity)
|
|
||||||
.select()
|
|
||||||
.where { betId eq UUID.fromString(betid) }
|
|
||||||
.map { row ->
|
|
||||||
Participation(
|
|
||||||
row[id].toString(),
|
|
||||||
row[betId].toString(),
|
|
||||||
row[username].toString(),
|
|
||||||
row[answer].toString(),
|
|
||||||
row[stake] ?: 0,
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getParticipationEntityFromUserId(user: String, betid: String): MutableList<Participation> {
|
|
||||||
return database.from(ParticipationsEntity)
|
|
||||||
.select()
|
|
||||||
.where { (betId eq UUID.fromString(betid)) and (username eq user) }
|
|
||||||
.map { row ->
|
|
||||||
Participation(
|
|
||||||
row[id].toString(),
|
|
||||||
row[betId].toString(),
|
|
||||||
row[username].toString(),
|
|
||||||
row[answer].toString(),
|
|
||||||
row[stake] ?: 0,
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getParticipationEntity(): MutableList<Participation> {
|
|
||||||
return database.from(ParticipationsEntity).select().map {
|
|
||||||
row -> Participation(
|
|
||||||
row[id].toString(),
|
|
||||||
row[betId].toString(),
|
|
||||||
row[username].toString(),
|
|
||||||
row[answer].toString(),
|
|
||||||
row[stake]?:0,
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteParticipation(participation: Participation): Boolean {
|
|
||||||
val deletedCount = database.delete(ParticipationsEntity) {
|
|
||||||
it.id eq UUID.fromString(participation.id)
|
|
||||||
}
|
|
||||||
return deletedCount > 0
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package allin.entities
|
|
||||||
|
|
||||||
import allin.database
|
|
||||||
import allin.utils.Execute
|
|
||||||
import org.ktorm.dsl.*
|
|
||||||
import org.ktorm.entity.Entity
|
|
||||||
import org.ktorm.schema.Table
|
|
||||||
import org.ktorm.schema.uuid
|
|
||||||
import org.ktorm.schema.varchar
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
interface ResponseEntity : Entity<ResponseEntity> {
|
|
||||||
val betId: UUID
|
|
||||||
val response: String
|
|
||||||
}
|
|
||||||
|
|
||||||
object ResponsesEntity : Table<ResponseEntity>("response") {
|
|
||||||
val id = uuid("id").primaryKey()
|
|
||||||
val response = varchar("response").primaryKey()
|
|
||||||
fun createResponseTable(){
|
|
||||||
val request="CREATE TABLE IF NOT EXISTS response (id UUID,response VARCHAR(250),CONSTRAINT pk_response_id PRIMARY KEY (id,response));"
|
|
||||||
database.Execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getResponse(idBet: UUID): MutableList<String> {
|
|
||||||
return database.from(ResponsesEntity)
|
|
||||||
.select(response)
|
|
||||||
.where { id eq idBet }
|
|
||||||
.map { it[response].toString() }.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addResponse(responses : MutableList<String>, idBet : UUID ) {
|
|
||||||
responses.forEach {selected ->
|
|
||||||
database.insert(ResponsesEntity) {
|
|
||||||
set(it.id, idBet)
|
|
||||||
set(it.response,selected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package allin.entities
|
|
||||||
|
|
||||||
import allin.database
|
|
||||||
import allin.dto.UserDTO
|
|
||||||
import allin.model.User
|
|
||||||
import allin.utils.Execute
|
|
||||||
import org.ktorm.dsl.*
|
|
||||||
import org.ktorm.entity.*
|
|
||||||
import org.ktorm.schema.*
|
|
||||||
import java.util.UUID.fromString
|
|
||||||
|
|
||||||
interface UserEntity : Entity<UserEntity> {
|
|
||||||
val username: String
|
|
||||||
var email: String
|
|
||||||
var password: String
|
|
||||||
var nbCoins: Int
|
|
||||||
}
|
|
||||||
object UsersEntity : Table<UserEntity>("utilisateur") {
|
|
||||||
val id = uuid("id").primaryKey()
|
|
||||||
val username = varchar("username")
|
|
||||||
val password = varchar("password")
|
|
||||||
val nbCoins = int("coins")
|
|
||||||
val email = varchar("email")
|
|
||||||
|
|
||||||
fun getUserToUserDTO(): MutableList<UserDTO> {
|
|
||||||
return database.from(UsersEntity).select().map {
|
|
||||||
row -> UserDTO(
|
|
||||||
row[id].toString(),
|
|
||||||
row[username].toString(),
|
|
||||||
row[email].toString(),
|
|
||||||
row[nbCoins]?:0,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createUserTable(){
|
|
||||||
val request="CREATE TABLE IF not exists utilisateur ( id uuid PRIMARY KEY, username VARCHAR(255), password VARCHAR(255),coins double precision,email VARCHAR(255))"
|
|
||||||
database.Execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun modifyCoins(user: String, cost : Int){
|
|
||||||
val request = "UPDATE utilisateur SET coins = coins - $cost WHERE username = '$user';"
|
|
||||||
database.Execute(request)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUserByUsernameAndPassword(login: String): Pair<UserDTO?, String?> {
|
|
||||||
return database.from(UsersEntity)
|
|
||||||
.select()
|
|
||||||
.where { (username eq login) /*and (password eq passwordParam)*/ }
|
|
||||||
.map { row ->
|
|
||||||
Pair(
|
|
||||||
UserDTO(
|
|
||||||
row[id].toString(),
|
|
||||||
row[username].toString(),
|
|
||||||
row[email].toString(),
|
|
||||||
row[nbCoins]?:0,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
row[password].toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.firstOrNull() ?: Pair(null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addUserEntity(user : User){
|
|
||||||
database.insert(UsersEntity){
|
|
||||||
set(it.id,fromString(user.id))
|
|
||||||
set(it.nbCoins,user.nbCoins)
|
|
||||||
set(it.username,user.username)
|
|
||||||
set(it.password,user.password)
|
|
||||||
set(it.email,user.email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun deleteUserByUsername(username: String): Boolean {
|
|
||||||
val deletedCount = database.delete(UsersEntity) {
|
|
||||||
it.username eq username
|
|
||||||
}
|
|
||||||
return deletedCount > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
|||||||
|
package allin.ext
|
||||||
|
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.expression.FunctionExpression
|
||||||
|
import org.ktorm.schema.ColumnDeclaring
|
||||||
|
import org.ktorm.schema.IntSqlType
|
||||||
|
import org.ktorm.schema.VarcharSqlType
|
||||||
|
import java.sql.ResultSet
|
||||||
|
|
||||||
|
fun Database.executeWithResult(request: String): ResultSet? {
|
||||||
|
try {
|
||||||
|
if (request.isNotEmpty()) {
|
||||||
|
return this.useTransaction { transaction ->
|
||||||
|
val connection = transaction.connection
|
||||||
|
val resultSet = connection.prepareStatement(request).executeQuery()
|
||||||
|
resultSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println(e.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Database.execute(request: String) {
|
||||||
|
if (request.isNotEmpty())
|
||||||
|
this.useTransaction {
|
||||||
|
val connection = it.connection
|
||||||
|
connection.prepareStatement(request).execute()
|
||||||
|
connection.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnDeclaring<String>.length(): FunctionExpression<Int> {
|
||||||
|
return FunctionExpression(
|
||||||
|
functionName = "LENGTH",
|
||||||
|
arguments = listOf(this.asExpression()),
|
||||||
|
sqlType = IntSqlType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnDeclaring<String>.toLowerCase(): FunctionExpression<String> {
|
||||||
|
return FunctionExpression(
|
||||||
|
functionName = "LOWER",
|
||||||
|
arguments = listOf(this.asExpression()),
|
||||||
|
sqlType = VarcharSqlType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnDeclaring<String>.toUpperCase(): FunctionExpression<String> {
|
||||||
|
return FunctionExpression(
|
||||||
|
functionName = "UPPER",
|
||||||
|
arguments = listOf(this.asExpression()),
|
||||||
|
sqlType = VarcharSqlType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnDeclaring<String>.levenshteinLessEq(
|
||||||
|
target: ColumnDeclaring<String>,
|
||||||
|
max: ColumnDeclaring<Int>
|
||||||
|
): FunctionExpression<Int> {
|
||||||
|
return FunctionExpression(
|
||||||
|
functionName = "levenshtein_less_equal",
|
||||||
|
arguments = listOf(this.asExpression(), target.asExpression(), max.asExpression()),
|
||||||
|
sqlType = IntSqlType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnDeclaring<String>.levenshteinLessEq(target: String, max: ColumnDeclaring<Int>): FunctionExpression<Int> =
|
||||||
|
levenshteinLessEq(
|
||||||
|
wrapArgument(target),
|
||||||
|
max
|
||||||
|
)
|
@ -1,14 +1,27 @@
|
|||||||
package allin.model
|
package allin.model
|
||||||
|
|
||||||
object ApiMessage {
|
object ApiMessage {
|
||||||
const val Welcome = "Welcome on AllIn's API !"
|
const val WELCOME = "Welcome on AllIn's API !"
|
||||||
const val TokenUserNotFound = "User not found with the valid token !"
|
const val TOKEN_USER_NOT_FOUND = "User not found with the valid token !"
|
||||||
const val UserNotFound = "User not found."
|
const val USER_NOT_FOUND = "User not found."
|
||||||
const val BetNotFound = "Bet not found."
|
const val BET_NOT_FOUND = "Bet not found."
|
||||||
const val BetAlreadyExist = "Bet already exists."
|
const val BET_ALREADY_EXIST = "Bet already exists."
|
||||||
const val IncorrectLoginPassword = "Login and/or password incorrect."
|
const val INCORRECT_LOGIN_PASSWORD = "Login and/or password incorrect."
|
||||||
const val UserAlreadyExist = "Mail and/or username already exists."
|
const val USER_ALREADY_EXISTS = "Username already exists."
|
||||||
const val InvalidMail = "Invalid mail."
|
const val MAIL_ALREADY_EXISTS = "Mail already exists."
|
||||||
const val ParticipationNotFound = "Participation not found."
|
const val INVALID_MAIL = "Invalid mail."
|
||||||
const val NotEnoughCoins = "Not enough coins."
|
const val PARTICIPATION_NOT_FOUND = "Participation not found."
|
||||||
|
const val NOT_ENOUGH_COINS = "Not enough coins."
|
||||||
|
const val NO_GIFT = "Can't get daily gift."
|
||||||
|
const val USER_CANT_BE_DELETE = "This user can't be delete now !"
|
||||||
|
const val FRIENDS_ALREADY_EXISTS = "User already exists in your Friends List."
|
||||||
|
const val FRIENDS_DOESNT_EXISTS = "User doesn't exists in your Friends List."
|
||||||
|
const val FRIENDS_REQUEST_HIMSELF = "The receiver can't be the sender."
|
||||||
|
const val FILE_NOT_FOUND = "File not found."
|
||||||
|
const val USER_DOESNT_HAVE_PERMISSION = "This user can't delete or modify this."
|
||||||
|
const val JWT_TOKEN_INFO = "JWT token of the logged user"
|
||||||
|
const val ID_BET_INFO = "Id of the desired bet"
|
||||||
|
const val BET_NOT_FOUND_INFO = "Bet not found in the selected source"
|
||||||
|
const val NOT_CREATOR_INFO = "The user is not the creator of the bet"
|
||||||
|
const val USER_UPDATE_INFO = "New User information"
|
||||||
}
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
enum class BetFilter {
|
||||||
|
PUBLIC,
|
||||||
|
INVITATION,
|
||||||
|
IN_PROGRESS,
|
||||||
|
FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BetFiltersRequest(
|
||||||
|
val filters: List<BetFilter>
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BetInfo(
|
||||||
|
var id: String,
|
||||||
|
var totalStakes: Int,
|
||||||
|
var totalParticipants: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BetAnswerInfo(
|
||||||
|
val betId: String,
|
||||||
|
val response: String,
|
||||||
|
val totalStakes: Int,
|
||||||
|
val odds: Float
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BetResult(
|
||||||
|
val betId: String,
|
||||||
|
val result: String
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
enum class BetStatus {
|
||||||
|
IN_PROGRESS,
|
||||||
|
WAITING,
|
||||||
|
CLOSING,
|
||||||
|
FINISHED,
|
||||||
|
CANCELLED
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
enum class BetType {
|
||||||
|
MATCH,
|
||||||
|
BINARY,
|
||||||
|
CUSTOM
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
|
||||||
|
data class Friend(
|
||||||
|
val sender: String,
|
||||||
|
val receiver: String
|
||||||
|
)
|
@ -0,0 +1,7 @@
|
|||||||
|
package allin.model
|
||||||
|
|
||||||
|
enum class FriendStatus {
|
||||||
|
FRIEND,
|
||||||
|
REQUESTED,
|
||||||
|
NOT_FRIEND
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package allin.routing
|
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Application.BasicRouting(){
|
|
||||||
routing {
|
|
||||||
get("/") {
|
|
||||||
call.respond("Bienvenue sur l'API de AlLin!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package allin.routing
|
|
||||||
|
|
||||||
import allin.entities.BetsEntity.getBets
|
|
||||||
import allin.entities.ParticipationsEntity.getParticipationEntityFromBetId
|
|
||||||
import allin.entities.ParticipationsEntity.getParticipationEntityFromUserId
|
|
||||||
import allin.ext.hasToken
|
|
||||||
import allin.ext.verifyUserFromToken
|
|
||||||
import allin.model.BetDetail
|
|
||||||
import allin.model.getBetAnswerDetail
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Application.BetDetailRouter() {
|
|
||||||
routing {
|
|
||||||
authenticate {
|
|
||||||
get("/betdetail/get/{id}") {
|
|
||||||
hasToken { principal ->
|
|
||||||
verifyUserFromToken(principal) { user, _ ->
|
|
||||||
val id = call.parameters["id"].toString()
|
|
||||||
val participations = getParticipationEntityFromBetId(id)
|
|
||||||
val selectedBet = getBets().find { it.id == id }
|
|
||||||
if (selectedBet != null) {
|
|
||||||
call.respond(
|
|
||||||
HttpStatusCode.Accepted,
|
|
||||||
BetDetail(
|
|
||||||
selectedBet,
|
|
||||||
getBetAnswerDetail(selectedBet, participations),
|
|
||||||
participations.toList(),
|
|
||||||
getParticipationEntityFromUserId(user.username, id).lastOrNull()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.NotFound, "Bet not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
|||||||
package allin.routing
|
|
||||||
|
|
||||||
import allin.entities.BetsEntity.addBetEntity
|
|
||||||
import allin.entities.BetsEntity.getBets
|
|
||||||
import allin.entities.ParticipationsEntity.getParticipationEntity
|
|
||||||
import allin.ext.hasToken
|
|
||||||
import allin.ext.verifyUserFromToken
|
|
||||||
import allin.model.ApiMessage
|
|
||||||
import allin.model.Bet
|
|
||||||
import allin.model.UpdatedBetData
|
|
||||||
import allin.utils.AppConfig
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
val tokenManagerBet = AppConfig.tokenManager
|
|
||||||
|
|
||||||
fun Application.BetRouter() {
|
|
||||||
routing {
|
|
||||||
route("/bets/add") {
|
|
||||||
authenticate {
|
|
||||||
post {
|
|
||||||
hasToken { principal ->
|
|
||||||
val bet = call.receive<Bet>()
|
|
||||||
val id = UUID.randomUUID().toString()
|
|
||||||
val username = tokenManagerBet.getUsernameFromToken(principal)
|
|
||||||
val bets = getBets()
|
|
||||||
bets.find { it.id == id }?.let {
|
|
||||||
call.respond(HttpStatusCode.Conflict, ApiMessage.BetAlreadyExist)
|
|
||||||
} ?: run {
|
|
||||||
val betWithId = Bet(
|
|
||||||
id,
|
|
||||||
bet.theme,
|
|
||||||
bet.sentenceBet,
|
|
||||||
bet.endRegistration,
|
|
||||||
bet.endBet,
|
|
||||||
bet.isPrivate,
|
|
||||||
bet.response,
|
|
||||||
username
|
|
||||||
)
|
|
||||||
addBetEntity(betWithId)
|
|
||||||
call.respond(HttpStatusCode.Created, betWithId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route("/bets/gets") {
|
|
||||||
get {
|
|
||||||
// if(bets.size>0)
|
|
||||||
val bets= getBets()
|
|
||||||
call.respond(HttpStatusCode.Accepted, bets.toList())
|
|
||||||
// else call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route("/bets/get/{id}") {
|
|
||||||
get {
|
|
||||||
val bets= getBets()
|
|
||||||
val id = call.parameters["id"] ?: ""
|
|
||||||
bets.find { it.id == id }?.let { bet ->
|
|
||||||
call.respond(HttpStatusCode.Accepted, bet)
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route("/bets/delete") {
|
|
||||||
post {
|
|
||||||
val idbet = call.receive<Map<String, String>>()["id"]
|
|
||||||
val bets= getBets()
|
|
||||||
bets.find { it.id == idbet }?.let { findbet ->
|
|
||||||
bets.remove(findbet)
|
|
||||||
call.respond(HttpStatusCode.Accepted, findbet)
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("bets/update") {
|
|
||||||
post {
|
|
||||||
val updatedBetData = call.receive<UpdatedBetData>()
|
|
||||||
val bets= getBets()
|
|
||||||
bets.find { it.id == updatedBetData.id }?.let { findbet ->
|
|
||||||
findbet.endBet = updatedBetData.endBet
|
|
||||||
findbet.isPrivate = updatedBetData.isPrivate
|
|
||||||
findbet.response = updatedBetData.response
|
|
||||||
call.respond(HttpStatusCode.Accepted, findbet)
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate {
|
|
||||||
get("/bets/current") {
|
|
||||||
val bets= getBets()
|
|
||||||
hasToken { principal ->
|
|
||||||
verifyUserFromToken(principal) { user, _ ->
|
|
||||||
val bets = getParticipationEntity()
|
|
||||||
.filter { it.username == user.username }
|
|
||||||
.mapNotNull { itParticipation -> bets.find { it.id == itParticipation.betId } }
|
|
||||||
call.respond(HttpStatusCode.OK, bets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package allin.routing
|
|
||||||
|
|
||||||
import allin.entities.ParticipationsEntity.addParticipationEntity
|
|
||||||
import allin.entities.ParticipationsEntity.deleteParticipation
|
|
||||||
import allin.entities.ParticipationsEntity.getParticipationEntity
|
|
||||||
import allin.entities.UsersEntity.modifyCoins
|
|
||||||
import allin.ext.hasToken
|
|
||||||
import allin.ext.verifyUserFromToken
|
|
||||||
import allin.model.ApiMessage
|
|
||||||
import allin.model.Participation
|
|
||||||
import allin.model.ParticipationRequest
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
fun Application.ParticipationRouter() {
|
|
||||||
routing {
|
|
||||||
authenticate {
|
|
||||||
post("/participations/add") {
|
|
||||||
hasToken { principal ->
|
|
||||||
val participation = call.receive<ParticipationRequest>()
|
|
||||||
verifyUserFromToken(principal) { user, _ ->
|
|
||||||
if (user.nbCoins >= participation.stake) {
|
|
||||||
addParticipationEntity(
|
|
||||||
Participation(
|
|
||||||
id = UUID.randomUUID().toString(),
|
|
||||||
betId = participation.betId,
|
|
||||||
username = user.username,
|
|
||||||
answer = participation.answer,
|
|
||||||
stake = participation.stake
|
|
||||||
)
|
|
||||||
)
|
|
||||||
modifyCoins(user.username,participation.stake)
|
|
||||||
call.respond(HttpStatusCode.Created)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.Forbidden, ApiMessage.NotEnoughCoins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete("/participations/delete") {
|
|
||||||
hasToken { principal ->
|
|
||||||
val participationId = call.receive<String>()
|
|
||||||
getParticipationEntity().find { it.id == participationId }?.let { participation ->
|
|
||||||
verifyUserFromToken(principal) { _, _ ->
|
|
||||||
deleteParticipation(participation)
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.ParticipationNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package allin.routing
|
|
||||||
|
|
||||||
import allin.entities.UsersEntity.addUserEntity
|
|
||||||
import allin.entities.UsersEntity.deleteUserByUsername
|
|
||||||
import allin.entities.UsersEntity.getUserByUsernameAndPassword
|
|
||||||
import allin.entities.UsersEntity.getUserToUserDTO
|
|
||||||
import allin.ext.hasToken
|
|
||||||
import allin.ext.verifyUserFromToken
|
|
||||||
import allin.model.ApiMessage
|
|
||||||
import allin.model.CheckUser
|
|
||||||
import allin.model.User
|
|
||||||
import allin.model.UserRequest
|
|
||||||
import allin.utils.AppConfig
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
val RegexCheckerUser = AppConfig.regexChecker
|
|
||||||
val CryptManagerUser = AppConfig.cryptManager
|
|
||||||
val tokenManagerUser = AppConfig.tokenManager
|
|
||||||
const val DEFAULT_COINS = 500
|
|
||||||
fun Application.UserRouter() {
|
|
||||||
|
|
||||||
routing {
|
|
||||||
route("/users/register") {
|
|
||||||
post {
|
|
||||||
val tempUser = call.receive<UserRequest>()
|
|
||||||
if (RegexCheckerUser.isEmailInvalid(tempUser.email)) {
|
|
||||||
call.respond(HttpStatusCode.Forbidden, ApiMessage.InvalidMail)
|
|
||||||
}
|
|
||||||
val users = getUserToUserDTO()
|
|
||||||
users.find { it.username == tempUser.username || it.email == tempUser.email }?.let { _ ->
|
|
||||||
call.respond(HttpStatusCode.Conflict, ApiMessage.UserAlreadyExist)
|
|
||||||
} ?: run {
|
|
||||||
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)
|
|
||||||
addUserEntity(user)
|
|
||||||
call.respond(HttpStatusCode.Created, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route("/users/login") {
|
|
||||||
post {
|
|
||||||
val checkUser = call.receive<CheckUser>()
|
|
||||||
val user = getUserByUsernameAndPassword(checkUser.login)
|
|
||||||
if (CryptManagerUser.passwordDecrypt(user.second ?: "", checkUser.password)) {
|
|
||||||
user.first?.let { userDtoWithToken ->
|
|
||||||
userDtoWithToken.token = tokenManagerUser.generateOrReplaceJWTToken(userDtoWithToken)
|
|
||||||
call.respond(HttpStatusCode.OK, userDtoWithToken)
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiMessage.UserNotFound)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.NotFound, ApiMessage.IncorrectLoginPassword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate {
|
|
||||||
post("/users/delete") {
|
|
||||||
hasToken { principal ->
|
|
||||||
verifyUserFromToken(principal) { _, password ->
|
|
||||||
val checkUser = call.receive<CheckUser>()
|
|
||||||
|
|
||||||
if (CryptManagerUser.passwordDecrypt(password, checkUser.password)) {
|
|
||||||
if (!deleteUserByUsername(checkUser.login)) {
|
|
||||||
call.respond(HttpStatusCode.InternalServerError, "This user can't be delete now !")
|
|
||||||
}
|
|
||||||
call.respond(HttpStatusCode.Accepted, password)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.NotFound, "Login and/or password incorrect.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/users/token") {
|
|
||||||
hasToken { principal ->
|
|
||||||
verifyUserFromToken(principal) { userDto, _ ->
|
|
||||||
call.respond(HttpStatusCode.OK, userDto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.model.ApiMessage
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.get
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Application.basicRouter() {
|
||||||
|
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
get("/", {
|
||||||
|
description = "Hello World of Allin API"
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
description = "Successful Request"
|
||||||
|
}
|
||||||
|
HttpStatusCode.InternalServerError to {
|
||||||
|
description = "Something unexpected happened"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing","Get '/'")
|
||||||
|
call.respond(ApiMessage.WELCOME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.dataSource
|
||||||
|
import allin.ext.hasToken
|
||||||
|
import allin.ext.verifyUserFromToken
|
||||||
|
import allin.model.ApiMessage
|
||||||
|
import allin.model.BetDetail
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.get
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
fun Application.betDetailRouter() {
|
||||||
|
val userDataSource = this.dataSource.userDataSource
|
||||||
|
val betDataSource = this.dataSource.betDataSource
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
authenticate {
|
||||||
|
get("/betdetail/get/{id}", {
|
||||||
|
description = "Retrieves the details of a specific bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>("JWT token of the logged user")
|
||||||
|
pathParameter<UUID>("Id of the desired detail bet")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The bet can be returned"
|
||||||
|
body<BetDetail>()
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = "Bet not found in the selected source"
|
||||||
|
body(ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /betdetail/get/{id}")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
val id = call.parameters["id"].toString()
|
||||||
|
val result = betDataSource.getBetDetailById(id, user.id)
|
||||||
|
if (result != null) {
|
||||||
|
logManager.log("Routing", "ACCEPTED GET /betdetail/get/{id}\t${result}")
|
||||||
|
call.respond(
|
||||||
|
HttpStatusCode.Accepted,
|
||||||
|
result
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} GET /betdetail/get/{id}")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,419 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.dataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.ext.hasToken
|
||||||
|
import allin.ext.verifyUserFromToken
|
||||||
|
import allin.model.*
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.get
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val tokenManagerBet = AppConfig.tokenManager
|
||||||
|
|
||||||
|
fun Application.betRouter() {
|
||||||
|
val userDataSource = this.dataSource.userDataSource
|
||||||
|
val betDataSource = this.dataSource.betDataSource
|
||||||
|
val participationDataSource = this.dataSource.participationDataSource
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
authenticate {
|
||||||
|
post("/bets/add", {
|
||||||
|
description = "Allows a user to create a new bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<Bet> {
|
||||||
|
description = "Bet to add in the selected source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Created to {
|
||||||
|
description = "the bet has been added"
|
||||||
|
body<Bet> {
|
||||||
|
description = "Bet with assigned id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.Conflict to {
|
||||||
|
description = "Id already exist"
|
||||||
|
body(ApiMessage.BET_ALREADY_EXIST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/add")
|
||||||
|
hasToken { principal ->
|
||||||
|
val bet = call.receive<Bet>()
|
||||||
|
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")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.BET_ALREADY_EXIST)
|
||||||
|
} ?: run {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authenticate {
|
||||||
|
post("/bets/gets", {
|
||||||
|
description = "Allows you to recover all bets"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<List<BetFilter>> {
|
||||||
|
description = "List of filters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The list of bets is available"
|
||||||
|
body<List<Bet>> {
|
||||||
|
description = "List of all bet in the selected source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/gets")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
val filtersRequest =
|
||||||
|
kotlin.runCatching { call.receiveNullable<BetFiltersRequest>() }.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, user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
get("/bets/popular", {
|
||||||
|
description = "Allows you to recover the most popular public bets"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The most popular public bet is available"
|
||||||
|
body<Bet> {
|
||||||
|
description = "The most popular public bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
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}")
|
||||||
|
call.respond(HttpStatusCode.Accepted, bet)
|
||||||
|
}
|
||||||
|
logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/popular")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/bets/get/{id}", {
|
||||||
|
description = "Retrieves a specific bet"
|
||||||
|
request {
|
||||||
|
pathParameter<String>(ApiMessage.ID_BET_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The bet is available"
|
||||||
|
body<Bet> {
|
||||||
|
description = "Desired bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = ApiMessage.BET_NOT_FOUND_INFO
|
||||||
|
body(ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
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}")
|
||||||
|
call.respond(HttpStatusCode.Accepted, bet)
|
||||||
|
} ?: logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/get/{id}")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/bets/delete", {
|
||||||
|
description = "Delete a specific bet"
|
||||||
|
request {
|
||||||
|
body<Map<String, String>> {
|
||||||
|
description = ApiMessage.ID_BET_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The bet has been deleted"
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = ApiMessage.BET_NOT_FOUND_INFO
|
||||||
|
body(ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/delete")
|
||||||
|
val id = call.receive<Map<String, String>>()["id"] ?: ""
|
||||||
|
if (betDataSource.removeBet(id)) {
|
||||||
|
logManager.log("Routing", "ACCEPTED /bets/delete")
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/delete")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("bets/update", {
|
||||||
|
description = "Update a specific bet"
|
||||||
|
request {
|
||||||
|
body<UpdatedBetData> {
|
||||||
|
description = "Information of the updated bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The bet has been updated"
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = ApiMessage.BET_NOT_FOUND_INFO
|
||||||
|
body(ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/update")
|
||||||
|
val updatedBetData = call.receive<UpdatedBetData>()
|
||||||
|
if (betDataSource.updateBet(updatedBetData)) {
|
||||||
|
logManager.log("Routing", "ACCEPTED /bets/delete")
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /bets/delete")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
get("/bets/toConfirm", {
|
||||||
|
description = "Allows a user to know which bets can be validated"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The list of bets that can be validated is available"
|
||||||
|
body<List<BetDetail>> {
|
||||||
|
description = "list of bets that can be validated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /bets/toConfirm")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
val response = betDataSource.getToConfirm(user)
|
||||||
|
logManager.log("Routing", "ACCEPTED /bets/toConfirm\t${response}")
|
||||||
|
call.respond(HttpStatusCode.Accepted, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
get("/bets/getWon", {
|
||||||
|
description = "Allows a user to know their won bets"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The list of won bets is available"
|
||||||
|
body<List<BetResultDetail>> {
|
||||||
|
description = "List of won bets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /bets/getWon")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
logManager.log("Routing", "ACCEPTED /bets/getWon")
|
||||||
|
call.respond(HttpStatusCode.Accepted, betDataSource.getWonNotifications(user.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
get("/bets/history", {
|
||||||
|
description = "Allows a user to know own history of bets"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "Bet history is available"
|
||||||
|
body<List<BetResultDetail>> {
|
||||||
|
description = "Betting history list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /bets/history")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
logManager.log(
|
||||||
|
"Routing",
|
||||||
|
"ACCEPTED /bets/toConfirm\t${betDataSource.getHistory(user.id)}"
|
||||||
|
)
|
||||||
|
call.respond(HttpStatusCode.Accepted, betDataSource.getHistory(user.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
get("/bets/current", {
|
||||||
|
description = "Allows a user to know current bets"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "List of current bets is available"
|
||||||
|
body<List<BetDetail>> {
|
||||||
|
description = "List of current bets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /bets/current")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
logManager.log(
|
||||||
|
"Routing",
|
||||||
|
"ACCEPTED /bets/toConfirm\t${betDataSource.getCurrent(user.id)}"
|
||||||
|
)
|
||||||
|
call.respond(HttpStatusCode.Accepted, betDataSource.getCurrent(user.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
post("/bets/confirm/{id}", {
|
||||||
|
description = "allows the creator of a bet to confirm the final answer"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
pathParameter<String>(ApiMessage.ID_BET_INFO)
|
||||||
|
body<String> {
|
||||||
|
description = "Final answer of the bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
description = "The final answer has been set"
|
||||||
|
}
|
||||||
|
HttpStatusCode.Unauthorized to {
|
||||||
|
description = ApiMessage.NOT_CREATOR_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /bets/confirm/{id}")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
val betId = call.parameters["id"] ?: ""
|
||||||
|
val result = call.receive<String>()
|
||||||
|
|
||||||
|
if (betDataSource.getBetById(betId)?.createdBy == user.username) {
|
||||||
|
betDataSource.confirmBet(betId, result)
|
||||||
|
logManager.log("Routing", "ACCEPTED /bets/confirm/{id} $result")
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "UNAUTHORIZED /bets/confirm/{id}")
|
||||||
|
call.respond(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/bets/users", {
|
||||||
|
description = "gets all userDTO of a bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
pathParameter<String>(ApiMessage.ID_BET_INFO)
|
||||||
|
body<List<UserDTO>> {
|
||||||
|
description = "UserDTO of the bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "List of 4 user of the selected bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/users")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { _, _ ->
|
||||||
|
val id = call.receive<Map<String, String>>()["id"] ?: ""
|
||||||
|
val participations = participationDataSource.getParticipationFromBetId(id)
|
||||||
|
val users =
|
||||||
|
participations.map { userDataSource.getUserById(it.id) }.toSet().take(4)
|
||||||
|
.toList()
|
||||||
|
call.respond(HttpStatusCode.Accepted, users)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/bets/pvbet/update", {
|
||||||
|
description = "Add new users to a private bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<UpdatedPrivateBet> {
|
||||||
|
description = "Bet id and list of new users"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "Invited users list updated"
|
||||||
|
}
|
||||||
|
HttpStatusCode.Unauthorized to {
|
||||||
|
description = ApiMessage.NOT_CREATOR_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /bets/pvbet/update")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
val updateRequest = call.receive<UpdatedPrivateBet>()
|
||||||
|
val bet = betDataSource.getBetById(updateRequest.betid)
|
||||||
|
if (user.username != bet?.createdBy) {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized, ApiMessage.USER_DOESNT_HAVE_PERMISSION)
|
||||||
|
}
|
||||||
|
betDataSource.addUserInPrivatebet(updateRequest)
|
||||||
|
call.respond(HttpStatusCode.Accepted, updateRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.dataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.ext.hasToken
|
||||||
|
import allin.ext.verifyUserFromToken
|
||||||
|
import allin.model.ApiMessage
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.get
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Application.friendRouter() {
|
||||||
|
|
||||||
|
val userDataSource = this.dataSource.userDataSource
|
||||||
|
val friendDataSource = this.dataSource.friendDataSource
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
authenticate {
|
||||||
|
get("/friends/gets", {
|
||||||
|
description = "Allows you to recover all friends of a JWT Token"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The list of friends is available"
|
||||||
|
body<List<UserDTO>> {
|
||||||
|
description = "List of friends"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /friends/gets")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { _, _ ->
|
||||||
|
val username = tokenManagerBet.getUsernameFromToken(principal)
|
||||||
|
val user = userDataSource.getUserByUsername(username).first
|
||||||
|
logManager.log(
|
||||||
|
"Routing",
|
||||||
|
"ACCEPTED /friends/gets\t${friendDataSource.getFriendFromUserId(user?.id.toString())}"
|
||||||
|
)
|
||||||
|
call.respond(HttpStatusCode.Accepted, friendDataSource.getFriendFromUserId(user?.id.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
get("/friends/requests", {
|
||||||
|
description = "Allows you to recover all friend requests of a JWT Token"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "The list of friend requests is available"
|
||||||
|
body<List<UserDTO>> {
|
||||||
|
description = "List of friend requests"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /friends/requests")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { _, _ ->
|
||||||
|
val username = tokenManagerBet.getUsernameFromToken(principal)
|
||||||
|
val user = userDataSource.getUserByUsername(username).first
|
||||||
|
logManager.log(
|
||||||
|
"Routing", "ACCEPTED /friends/requests\t${
|
||||||
|
friendDataSource.getFriendRequestsFromUserId(user?.id.toString())
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
call.respond(
|
||||||
|
HttpStatusCode.Accepted,
|
||||||
|
friendDataSource.getFriendRequestsFromUserId(user?.id.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
post("/friends/add", {
|
||||||
|
description = "Allows a user to add a friend"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<String> {
|
||||||
|
description = "User to add in the friends list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Created to {
|
||||||
|
description = "the friend has been added"
|
||||||
|
body<String> {
|
||||||
|
description = "Friend with assigned id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.Conflict to {
|
||||||
|
description = "Friend already exist in the friends list"
|
||||||
|
body(ApiMessage.FRIENDS_ALREADY_EXISTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /friends/add")
|
||||||
|
hasToken { principal ->
|
||||||
|
val requestMap = call.receive<Map<String, String>>()
|
||||||
|
val usernameFriend = requestMap["username"] ?: return@hasToken call.respond(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
"Username is missing"
|
||||||
|
)
|
||||||
|
val username = tokenManagerBet.getUsernameFromToken(principal)
|
||||||
|
|
||||||
|
val user = userDataSource.getUserByUsername(username).first
|
||||||
|
val userFriend = userDataSource.getUserByUsername(usernameFriend).first
|
||||||
|
|
||||||
|
if (user == null || userFriend == null) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.USER_NOT_FOUND} /friends/add")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.USER_NOT_FOUND)
|
||||||
|
} else if (userFriend == user) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.FRIENDS_REQUEST_HIMSELF} /friends/add")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.FRIENDS_REQUEST_HIMSELF)
|
||||||
|
} else {
|
||||||
|
val friendlist = friendDataSource.getFriendFromUserId(user.id)
|
||||||
|
if (friendlist.map { it.id }.contains(userFriend.id)) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.FRIENDS_ALREADY_EXISTS} /friends/add")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.FRIENDS_ALREADY_EXISTS)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "ACCEPTED /friends/add\t${usernameFriend}")
|
||||||
|
friendDataSource.addFriend(user.id, userFriend.id)
|
||||||
|
call.respond(HttpStatusCode.Created, usernameFriend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/friends/delete", {
|
||||||
|
description = "Allows a user to delete a friend"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<String> {
|
||||||
|
description = "User to delete in the friends list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Created to {
|
||||||
|
description = "the friend has been delete"
|
||||||
|
body<String> {
|
||||||
|
description = "Friend with assigned id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.Conflict to {
|
||||||
|
description = "Friend doesn't exist in the friends list"
|
||||||
|
body(ApiMessage.FRIENDS_DOESNT_EXISTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /friends/delete")
|
||||||
|
hasToken { principal ->
|
||||||
|
val requestMap = call.receive<Map<String, String>>()
|
||||||
|
val usernameFriend = requestMap["username"] ?: return@hasToken call.respond(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
"Username is missing"
|
||||||
|
)
|
||||||
|
val username = tokenManagerBet.getUsernameFromToken(principal)
|
||||||
|
|
||||||
|
val user = userDataSource.getUserByUsername(username).first
|
||||||
|
val userFriend = userDataSource.getUserByUsername(usernameFriend).first
|
||||||
|
|
||||||
|
if (user == null || userFriend == null) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.USER_NOT_FOUND} /friends/delete")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.USER_NOT_FOUND)
|
||||||
|
} else {
|
||||||
|
if (friendDataSource.deleteFriend(user.id, userFriend.id)) {
|
||||||
|
logManager.log("Routing", "ACCEPTED /friends/delete\t${usernameFriend}")
|
||||||
|
call.respond(HttpStatusCode.Created, usernameFriend)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.FRIENDS_DOESNT_EXISTS} /friends/delete")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.FRIENDS_DOESNT_EXISTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/friends/search/{search}", {
|
||||||
|
description = "Search for users based on username"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
pathParameter<String>("Search string")
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
body<List<UserDTO>> {
|
||||||
|
description = "Filtered users."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /friends/search/{search}")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { userDto, _ ->
|
||||||
|
val users = friendDataSource.filterUsersByUsername(
|
||||||
|
fromUserId = userDto.id,
|
||||||
|
search = call.parameters["search"] ?: ""
|
||||||
|
)
|
||||||
|
logManager.log("Routing", "ACCEPTED /friends/search/{search}\t${users}")
|
||||||
|
call.respond(HttpStatusCode.OK, users)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.dataSource
|
||||||
|
import allin.ext.hasToken
|
||||||
|
import allin.ext.verifyUserFromToken
|
||||||
|
import allin.model.ApiMessage
|
||||||
|
import allin.model.Participation
|
||||||
|
import allin.model.ParticipationRequest
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.delete
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
fun Application.participationRouter() {
|
||||||
|
|
||||||
|
val userDataSource = this.dataSource.userDataSource
|
||||||
|
val participationDataSource = this.dataSource.participationDataSource
|
||||||
|
val betDataSource = this.dataSource.betDataSource
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
authenticate {
|
||||||
|
post("/participations/add", {
|
||||||
|
description = "Allows a user to add a stake to a bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>("JWT token of the logged user")
|
||||||
|
body<ParticipationRequest> {
|
||||||
|
description = "Participation in a bet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Created to {
|
||||||
|
description = "The stake has been bet"
|
||||||
|
}
|
||||||
|
HttpStatusCode.Forbidden to {
|
||||||
|
description = "User does not have enough coins"
|
||||||
|
body(ApiMessage.NOT_ENOUGH_COINS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /participations/add")
|
||||||
|
hasToken { principal ->
|
||||||
|
val participation = call.receive<ParticipationRequest>()
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
|
||||||
|
if (betDataSource.getBetById(participation.betId) == null) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.BET_NOT_FOUND} /participations/add")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.BET_NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.nbCoins >= participation.stake) {
|
||||||
|
participationDataSource.addParticipation(
|
||||||
|
Participation(
|
||||||
|
id = UUID.randomUUID().toString(),
|
||||||
|
betId = participation.betId,
|
||||||
|
userId = user.id,
|
||||||
|
answer = participation.answer,
|
||||||
|
stake = participation.stake,
|
||||||
|
username = user.username
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
userDataSource.removeCoins(username = user.username, amount = participation.stake)
|
||||||
|
betDataSource.updatePopularityScore(participation.betId)
|
||||||
|
logManager.log("Routing", "CREATED /participations/add")
|
||||||
|
call.respond(HttpStatusCode.Created)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.NOT_ENOUGH_COINS} /participations/add")
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ApiMessage.NOT_ENOUGH_COINS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete("/participations/delete", {
|
||||||
|
description = "Allows to delete a participation to a bet"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>("JWT token of the logged user")
|
||||||
|
body<String> {
|
||||||
|
description = "Id of the participation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = "Participation was not found"
|
||||||
|
body(ApiMessage.PARTICIPATION_NOT_FOUND)
|
||||||
|
}
|
||||||
|
HttpStatusCode.NoContent to {
|
||||||
|
description = "The operation was successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "DELETE /participations/delete")
|
||||||
|
hasToken {
|
||||||
|
val participationId = call.receive<String>()
|
||||||
|
if (participationDataSource.deleteParticipation(participationId)) {
|
||||||
|
logManager.log("Routing", "ACCEPTED /participations/delete")
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.PARTICIPATION_NOT_FOUND} /participations/delete")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.PARTICIPATION_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,289 @@
|
|||||||
|
package allin.routing
|
||||||
|
|
||||||
|
import allin.dataSource
|
||||||
|
import allin.dto.UserDTO
|
||||||
|
import allin.ext.hasToken
|
||||||
|
import allin.ext.verifyUserFromToken
|
||||||
|
import allin.model.*
|
||||||
|
import allin.utils.AppConfig
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.get
|
||||||
|
import io.github.smiley4.ktorswaggerui.dsl.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val RegexCheckerUser = AppConfig.regexChecker
|
||||||
|
val CryptManagerUser = AppConfig.cryptManager
|
||||||
|
val tokenManagerUser = AppConfig.tokenManager
|
||||||
|
val imageManagerUser = AppConfig.imageManager
|
||||||
|
val urlManager = AppConfig.urlManager
|
||||||
|
|
||||||
|
const val DEFAULT_COINS = 500
|
||||||
|
|
||||||
|
|
||||||
|
fun Application.userRouter() {
|
||||||
|
|
||||||
|
val userDataSource = this.dataSource.userDataSource
|
||||||
|
val logManager = AppConfig.logManager
|
||||||
|
|
||||||
|
routing {
|
||||||
|
post("/users/register", {
|
||||||
|
description = "Allows a user to register"
|
||||||
|
request {
|
||||||
|
body<UserRequest> {
|
||||||
|
description = ApiMessage.USER_UPDATE_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Created to {
|
||||||
|
description = "User created"
|
||||||
|
body<User> {
|
||||||
|
description = "The new user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.Conflict to {
|
||||||
|
description = "Email or username already taken"
|
||||||
|
body(ApiMessage.USER_ALREADY_EXISTS)
|
||||||
|
}
|
||||||
|
HttpStatusCode.Forbidden to {
|
||||||
|
description = "Email invalid"
|
||||||
|
body(ApiMessage.INVALID_MAIL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /users/register")
|
||||||
|
val tempUser = call.receive<UserRequest>()
|
||||||
|
if (RegexCheckerUser.isEmailInvalid(tempUser.email)) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.INVALID_MAIL} /users/register")
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ApiMessage.INVALID_MAIL)
|
||||||
|
} else if (userDataSource.userExists(tempUser.username)) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.USER_ALREADY_EXISTS} /users/register")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.USER_ALREADY_EXISTS)
|
||||||
|
} else if (userDataSource.emailExists(tempUser.email)) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.MAIL_ALREADY_EXISTS} /users/register")
|
||||||
|
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,
|
||||||
|
bestWin = 0,
|
||||||
|
nbFriends = 0,
|
||||||
|
nbBets = 0,
|
||||||
|
)
|
||||||
|
CryptManagerUser.passwordCrypt(user)
|
||||||
|
user.token = tokenManagerUser.generateOrReplaceJWTToken(user)
|
||||||
|
userDataSource.addUser(user)
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/register\t${user}")
|
||||||
|
call.respond(HttpStatusCode.Created, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/users/login", {
|
||||||
|
description = "Allows a user to login"
|
||||||
|
request {
|
||||||
|
body<CheckUser> {
|
||||||
|
description = ApiMessage.USER_UPDATE_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
description = "User logged in"
|
||||||
|
body<UserDTO>()
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = "Invalid credentials"
|
||||||
|
body(ApiMessage.USER_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /users/login")
|
||||||
|
val checkUser = call.receive<CheckUser>()
|
||||||
|
val user = userDataSource.getUserByUsername(checkUser.login)
|
||||||
|
if (CryptManagerUser.passwordDecrypt(user.second ?: "", checkUser.password)) {
|
||||||
|
user.first?.let { userDtoWithToken ->
|
||||||
|
userDtoWithToken.token = tokenManagerUser.generateOrReplaceJWTToken(userDtoWithToken)
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/login\t${userDtoWithToken}")
|
||||||
|
call.respond(HttpStatusCode.OK, userDtoWithToken)
|
||||||
|
} ?: logManager.log("Routing", "${ApiMessage.USER_NOT_FOUND} /users/login")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.USER_NOT_FOUND)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.INCORRECT_LOGIN_PASSWORD} /users/login")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.INCORRECT_LOGIN_PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/users/images/{fileName}") {
|
||||||
|
logManager.log("Routing", "GET /users/images/{fileName}")
|
||||||
|
val fileName = call.parameters["fileName"]
|
||||||
|
val urlfile = "images/$fileName"
|
||||||
|
val file = File("$urlfile.png")
|
||||||
|
if (file.exists()) {
|
||||||
|
call.respondFile(file)
|
||||||
|
} else {
|
||||||
|
val imageBytes = userDataSource.getImage(fileName.toString())
|
||||||
|
if (imageBytes != null) {
|
||||||
|
imageManagerUser.saveImage(urlfile, imageBytes)
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/images/{fileName}")
|
||||||
|
call.respondFile(file)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.FILE_NOT_FOUND} /users/images/{fileName}")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.FILE_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
post("/users/delete", {
|
||||||
|
description = "Allow you to delete your account"
|
||||||
|
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<CheckUser> {
|
||||||
|
description = ApiMessage.USER_UPDATE_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.InternalServerError to {
|
||||||
|
description = "User can't be delete"
|
||||||
|
body(ApiMessage.USER_CANT_BE_DELETE)
|
||||||
|
}
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
body<String> {
|
||||||
|
description = "Password of the user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = "User not found"
|
||||||
|
body(ApiMessage.INCORRECT_LOGIN_PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /users/delete")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { _, password ->
|
||||||
|
val checkUser = call.receive<CheckUser>()
|
||||||
|
if (CryptManagerUser.passwordDecrypt(password, checkUser.password)) {
|
||||||
|
if (!userDataSource.deleteUser(checkUser.login)) {
|
||||||
|
logManager.log("Routing", "${ApiMessage.USER_CANT_BE_DELETE} /users/delete")
|
||||||
|
call.respond(HttpStatusCode.InternalServerError, ApiMessage.USER_CANT_BE_DELETE)
|
||||||
|
}
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/delete")
|
||||||
|
call.respond(HttpStatusCode.Accepted, password)
|
||||||
|
} else {
|
||||||
|
logManager.log("Routing", "${ApiMessage.INCORRECT_LOGIN_PASSWORD} /users/delete")
|
||||||
|
call.respond(HttpStatusCode.NotFound, ApiMessage.INCORRECT_LOGIN_PASSWORD)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/users/token", {
|
||||||
|
description = "Allows you to retrieve the user linked to a JWT token"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
body<UserDTO> {
|
||||||
|
description = "Limited user information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /users/token")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { userDto, _ ->
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/token\t${userDto}")
|
||||||
|
call.respond(HttpStatusCode.OK, userDto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get("/users/gift", {
|
||||||
|
description = "Allows you to collect your daily gift"
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.OK to {
|
||||||
|
description = "Daily gift allowed !"
|
||||||
|
body<Int> {
|
||||||
|
description = "Number of coins offered"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpStatusCode.MethodNotAllowed to {
|
||||||
|
description = "You can't have you daily gift now"
|
||||||
|
body(ApiMessage.NO_GIFT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "GET /users/gift")
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { userDto, _ ->
|
||||||
|
if (userDataSource.canHaveDailyGift(userDto.username)) {
|
||||||
|
val dailyGift = (DAILY_GIFT_MIN..DAILY_GIFT_MAX).random()
|
||||||
|
userDataSource.addCoins(userDto.username, dailyGift)
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/gift\t${dailyGift}")
|
||||||
|
call.respond(HttpStatusCode.OK, dailyGift)
|
||||||
|
logManager.log("Routing", "${ApiMessage.NO_GIFT} /users/gift")
|
||||||
|
} else call.respond(HttpStatusCode.MethodNotAllowed, ApiMessage.NO_GIFT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/users/images", {
|
||||||
|
description = "Allow you to add a profil image"
|
||||||
|
|
||||||
|
request {
|
||||||
|
headerParameter<JWTPrincipal>(ApiMessage.JWT_TOKEN_INFO)
|
||||||
|
body<CheckUser> {
|
||||||
|
description = ApiMessage.USER_UPDATE_INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response {
|
||||||
|
HttpStatusCode.Accepted to {
|
||||||
|
description = "Image added"
|
||||||
|
}
|
||||||
|
HttpStatusCode.NotFound to {
|
||||||
|
description = "User not found"
|
||||||
|
body(ApiMessage.INCORRECT_LOGIN_PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
logManager.log("Routing", "POST /users/images")
|
||||||
|
|
||||||
|
hasToken { principal ->
|
||||||
|
verifyUserFromToken(userDataSource, principal) { user, _ ->
|
||||||
|
|
||||||
|
val base64Image = call.receiveText()
|
||||||
|
|
||||||
|
val urlfile = "images/${user.id}"
|
||||||
|
val imageByteArray = imageManagerUser.saveImage(urlfile, base64Image)
|
||||||
|
if (imageByteArray != null && imageByteArray.isNotEmpty()) {
|
||||||
|
userDataSource.removeImage(user.id)
|
||||||
|
userDataSource.addImage(user.id, imageByteArray)
|
||||||
|
logManager.log("Routing", "ACCEPTED /users/images")
|
||||||
|
call.respond(HttpStatusCode.OK, "${urlManager.getURL()}users/${urlfile}")
|
||||||
|
}
|
||||||
|
logManager.log("Routing", "${ApiMessage.FILE_NOT_FOUND} /users/images")
|
||||||
|
call.respond(HttpStatusCode.Conflict, ApiMessage.FILE_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,14 @@
|
|||||||
package allin.utils
|
package allin.utils
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import io.ktor.server.config.HoconApplicationConfig
|
import io.ktor.server.config.*
|
||||||
|
|
||||||
object AppConfig {
|
object AppConfig {
|
||||||
val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
|
private val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
|
||||||
val tokenManager = TokenManager.getInstance(config)
|
val tokenManager = TokenManager.getInstance(config)
|
||||||
val regexChecker = RegexChecker()
|
val regexChecker = RegexChecker()
|
||||||
val cryptManager = CryptManager()
|
val cryptManager = CryptManager()
|
||||||
|
val imageManager = ImageManager()
|
||||||
|
val urlManager = URLManager()
|
||||||
|
val logManager = LogManager()
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package allin.utils
|
|
||||||
|
|
||||||
import allin.database
|
|
||||||
import org.ktorm.database.Database
|
|
||||||
|
|
||||||
fun Database.Execute(request: String){
|
|
||||||
if(!request.isNullOrEmpty())
|
|
||||||
database.useTransaction {
|
|
||||||
val connection = it.connection
|
|
||||||
connection.prepareStatement(request).execute()
|
|
||||||
connection.commit()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
package allin.utils
|
||||||
|
|
||||||
|
import allin.data.postgres.entities.usersimage
|
||||||
|
import allin.routing.imageManagerUser
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.entity.find
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ImageManager {
|
||||||
|
|
||||||
|
fun saveImage(urlfile: String, base64Image: String): ByteArray? {
|
||||||
|
val cleanedBase64Image = cleanBase64(base64Image)
|
||||||
|
val imageBytes = Base64.getDecoder().decode(cleanedBase64Image)
|
||||||
|
val file = File("${urlfile}.png")
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
file.writeBytes(imageBytes)
|
||||||
|
return imageBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveImage(urlfile: String, base64Image: ByteArray) {
|
||||||
|
val file = File("${urlfile}.png")
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
file.writeBytes(base64Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImage(userId: String, database: Database): String? {
|
||||||
|
val imageByte = database.usersimage.find { it.id eq userId }?.image ?: return null
|
||||||
|
val urlfile = "images/$userId"
|
||||||
|
if (!imageManagerUser.imageAvailable(urlfile)) {
|
||||||
|
imageManagerUser.saveImage(urlfile, imageByte)
|
||||||
|
}
|
||||||
|
return "${AppConfig.urlManager.getURL()}users/${urlfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun imageAvailable(urlfile: String) = File(urlfile).exists()
|
||||||
|
|
||||||
|
fun cleanBase64(base64Image: String) = base64Image.replace("\n", "").replace("\r", "")
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package allin.utils
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class LogManager {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
|
||||||
|
|
||||||
|
fun log(type: String,message: String) {
|
||||||
|
println("[${LocalDateTime.now().format(formatter)}] [${type}] $message")
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,10 @@
|
|||||||
package allin.utils
|
package allin.utils
|
||||||
|
|
||||||
class RegexChecker {
|
class RegexChecker {
|
||||||
|
|
||||||
private val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
|
private val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
|
||||||
|
|
||||||
|
|
||||||
fun isEmailInvalid(email: String): Boolean {
|
fun isEmailInvalid(email: String): Boolean {
|
||||||
val emailRegex = Regex(emailRegex)
|
val emailRegex = Regex(emailRegex)
|
||||||
return !emailRegex.matches(email)
|
return !emailRegex.matches(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package allin.utils
|
||||||
|
|
||||||
|
import allin.hostIP
|
||||||
|
import allin.hostPort
|
||||||
|
import allin.isCodeFirstContainer
|
||||||
|
|
||||||
|
class URLManager {
|
||||||
|
fun getURL(): String {
|
||||||
|
return if (isCodeFirstContainer.isEmpty()) {
|
||||||
|
"http://$hostIP:$hostPort/"
|
||||||
|
} else "https://codefirst.iut.uca.fr${isCodeFirstContainer}"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package allin.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun kronJob(duration: Duration, action: () -> Unit) =
|
||||||
|
GlobalScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
while (true) {
|
||||||
|
runCatching { action() }
|
||||||
|
delay(duration.inWholeMilliseconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
secret="secret"
|
secret="secret"
|
||||||
issuer="http://0.0.0.0:8080/"
|
issuer="http://0.0.0.0:8080/"
|
||||||
audience="http://0.0.0.0:8080/"
|
audience="http://0.0.0.0:8080/"
|
||||||
realm="Access to main page"
|
realm="allin"
|
@ -1,4 +1,6 @@
|
|||||||
package allin
|
package allin
|
||||||
|
|
||||||
class ApplicationTest {
|
class ApplicationTest {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package allin.data.postgres
|
||||||
|
|
||||||
|
import allin.data.postgres.entities.betAnswerInfos
|
||||||
|
import allin.data.postgres.entities.betInfos
|
||||||
|
import allin.data.postgres.entities.betResults
|
||||||
|
import allin.model.*
|
||||||
|
import org.junit.jupiter.api.*
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.entity.removeIf
|
||||||
|
import org.ktorm.support.postgresql.PostgreSqlDialect
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||||
|
class PostgresBetDataSourceTest {
|
||||||
|
|
||||||
|
private lateinit var database: Database
|
||||||
|
private lateinit var dataSource: PostgresBetDataSource
|
||||||
|
lateinit var user: User
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
fun setUp() {
|
||||||
|
val dbDatabase = System.getenv()["POSTGRES_DB"]
|
||||||
|
val dbUser = System.getenv()["POSTGRES_USER"]
|
||||||
|
val dbPassword = System.getenv()["POSTGRES_PASSWORD"]
|
||||||
|
val dbHost = System.getenv()["POSTGRES_HOST"]
|
||||||
|
val url = "jdbc:postgresql://$dbHost/$dbDatabase"
|
||||||
|
|
||||||
|
database = Database.connect(
|
||||||
|
url = url,
|
||||||
|
user = dbUser,
|
||||||
|
password = dbPassword,
|
||||||
|
dialect = PostgreSqlDialect()
|
||||||
|
)
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
id = "123",
|
||||||
|
username = "JohnDoe",
|
||||||
|
email = "johndoe@example.com",
|
||||||
|
password = "securePassword123",
|
||||||
|
nbCoins = 1000,
|
||||||
|
token = null,
|
||||||
|
image = null,
|
||||||
|
bestWin = 500,
|
||||||
|
nbBets = 50,
|
||||||
|
nbFriends = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
dataSource = PostgresBetDataSource(database)
|
||||||
|
PostgresUserDataSource(database).addUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
fun testAddBet() {
|
||||||
|
val bet = Bet(
|
||||||
|
id = "bbba08f7-744f-4d23-9706-b31bdf24f614",
|
||||||
|
theme = "Sports",
|
||||||
|
sentenceBet = "Will team A win?",
|
||||||
|
type = BetType.BINARY,
|
||||||
|
endRegistration = ZonedDateTime.now(ZoneId.of("+02:00")).plusDays(1),
|
||||||
|
endBet = ZonedDateTime.now(ZoneId.of("+02:00")).plusDays(2),
|
||||||
|
isPrivate = false,
|
||||||
|
response = listOf(YES_VALUE, NO_VALUE),
|
||||||
|
createdBy = user.id
|
||||||
|
)
|
||||||
|
dataSource.addBet(bet)
|
||||||
|
|
||||||
|
val retrievedBet = dataSource.getBetById("bbba08f7-744f-4d23-9706-b31bdf24f614")
|
||||||
|
assertNotNull(retrievedBet)
|
||||||
|
assertEquals("bbba08f7-744f-4d23-9706-b31bdf24f614", retrievedBet?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
fun testGetAllBets() {
|
||||||
|
val userDTO = user.toDto()
|
||||||
|
val bets = dataSource.getAllBets(emptyList(), userDTO)
|
||||||
|
assertTrue(bets.isNotEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
fun testUpdateBet() {
|
||||||
|
val updatedData = UpdatedBetData(
|
||||||
|
id = "bbba08f7-744f-4d23-9706-b31bdf24f614",
|
||||||
|
endBet = ZonedDateTime.now(ZoneId.of("+02:00")).plusDays(3),
|
||||||
|
isPrivate = true,
|
||||||
|
response = listOf(YES_VALUE, NO_VALUE)
|
||||||
|
)
|
||||||
|
val result = dataSource.updateBet(updatedData)
|
||||||
|
assertTrue(result)
|
||||||
|
|
||||||
|
val retrievedBet = dataSource.getBetById("bbba08f7-744f-4d23-9706-b31bdf24f614")
|
||||||
|
assertNotNull(retrievedBet)
|
||||||
|
assertTrue(retrievedBet?.isPrivate ?: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
fun testConfirmBet() {
|
||||||
|
dataSource.confirmBet("bbba08f7-744f-4d23-9706-b31bdf24f614", YES_VALUE)
|
||||||
|
|
||||||
|
val retrievedBet = dataSource.getBetById("bbba08f7-744f-4d23-9706-b31bdf24f614")
|
||||||
|
assertNotNull(retrievedBet)
|
||||||
|
assertEquals(BetStatus.FINISHED, retrievedBet?.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
fun testGetBetDetailById() {
|
||||||
|
val betDetail = dataSource.getBetDetailById("bbba08f7-744f-4d23-9706-b31bdf24f614", user.id)
|
||||||
|
assertNotNull(betDetail)
|
||||||
|
assertEquals("bbba08f7-744f-4d23-9706-b31bdf24f614", betDetail?.bet?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun tearDown() {
|
||||||
|
database.betResults.removeIf { it.betId eq "bbba08f7-744f-4d23-9706-b31bdf24f614" }
|
||||||
|
database.betInfos.removeIf { it.id eq "bbba08f7-744f-4d23-9706-b31bdf24f614" }
|
||||||
|
database.betAnswerInfos.removeIf { it.betId eq "bbba08f7-744f-4d23-9706-b31bdf24f614" }
|
||||||
|
dataSource.removeBet("bbba08f7-744f-4d23-9706-b31bdf24f614")
|
||||||
|
PostgresUserDataSource(database).deleteUser(user.username)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
import allin.data.postgres.PostgresUserDataSource
|
||||||
|
import allin.data.postgres.entities.UsersEntity
|
||||||
|
import allin.data.postgres.entities.users
|
||||||
|
import allin.ext.executeWithResult
|
||||||
|
import allin.model.User
|
||||||
|
import junit.framework.TestCase.*
|
||||||
|
import org.junit.jupiter.api.*
|
||||||
|
import org.ktorm.database.Database
|
||||||
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.dsl.update
|
||||||
|
import org.ktorm.entity.find
|
||||||
|
import org.ktorm.support.postgresql.PostgreSqlDialect
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||||
|
class PostgresUserDataSourceTest {
|
||||||
|
|
||||||
|
private lateinit var userDataSource: PostgresUserDataSource
|
||||||
|
private lateinit var database: Database
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
fun setUp() {
|
||||||
|
val dbDatabase = System.getenv()["POSTGRES_DB"]
|
||||||
|
val dbUser = System.getenv()["POSTGRES_USER"]
|
||||||
|
val dbPassword = System.getenv()["POSTGRES_PASSWORD"]
|
||||||
|
val dbHost = System.getenv()["POSTGRES_HOST"]
|
||||||
|
val url = "jdbc:postgresql://$dbHost/$dbDatabase"
|
||||||
|
|
||||||
|
database = Database.connect(
|
||||||
|
url = url,
|
||||||
|
user = dbUser,
|
||||||
|
password = dbPassword,
|
||||||
|
dialect = PostgreSqlDialect()
|
||||||
|
)
|
||||||
|
|
||||||
|
userDataSource = PostgresUserDataSource(database)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun delUser() {
|
||||||
|
userDataSource.deleteUser("JaneDoe")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
fun addUser() {
|
||||||
|
val user = User(
|
||||||
|
id = "123",
|
||||||
|
username = "JohnDoe",
|
||||||
|
email = "johndoe@example.com",
|
||||||
|
password = "securePassword123",
|
||||||
|
nbCoins = 1000,
|
||||||
|
token = null,
|
||||||
|
image = null,
|
||||||
|
bestWin = 500,
|
||||||
|
nbBets = 50,
|
||||||
|
nbFriends = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
userDataSource.addUser(user)
|
||||||
|
|
||||||
|
val addedUser = database.users.find { it.id eq "123" }
|
||||||
|
assertNotNull(addedUser)
|
||||||
|
assertEquals("JohnDoe", addedUser?.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
fun getUserByUsername() {
|
||||||
|
val result = userDataSource.getUserByUsername("JohnDoe")
|
||||||
|
|
||||||
|
assertNotNull(result.first)
|
||||||
|
assertEquals("JohnDoe", result.first?.username)
|
||||||
|
assertEquals("securePassword123", result.second)
|
||||||
|
|
||||||
|
val resultS = userDataSource.getUserByUsername("nonexistent")
|
||||||
|
|
||||||
|
assertNull(resultS.first)
|
||||||
|
assertNull(resultS.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
fun getUserById() {
|
||||||
|
val result = userDataSource.getUserById("123")
|
||||||
|
|
||||||
|
assertNotNull(result)
|
||||||
|
assertEquals("JohnDoe", result?.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
fun deleteUser() {
|
||||||
|
val result = userDataSource.deleteUser("JohnDoe")
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
|
||||||
|
val deletedUser = database.users.find { it.id eq "123" }
|
||||||
|
assertNull(deletedUser)
|
||||||
|
|
||||||
|
val resultS = userDataSource.deleteUser("nonexistent")
|
||||||
|
assertFalse(resultS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
fun addCoins() {
|
||||||
|
userDataSource.addUser(
|
||||||
|
User(
|
||||||
|
id = "11111",
|
||||||
|
username = "JaneDoe",
|
||||||
|
email = "janedoe@example.com",
|
||||||
|
password = "securePassword456",
|
||||||
|
nbCoins = 1000,
|
||||||
|
token = null,
|
||||||
|
image = null,
|
||||||
|
bestWin = 500,
|
||||||
|
nbBets = 50,
|
||||||
|
nbFriends = 10
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
userDataSource.addCoins("JaneDoe", 500)
|
||||||
|
|
||||||
|
val updatedUser = database.users.find { it.id eq "11111" }
|
||||||
|
assertNotNull(updatedUser)
|
||||||
|
assertEquals(1500, updatedUser?.nbCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
fun removeCoins() {
|
||||||
|
userDataSource.removeCoins("JaneDoe", 300)
|
||||||
|
val updatedUser = database.users.find { it.id eq "11111" }
|
||||||
|
assertNotNull(updatedUser)
|
||||||
|
assertEquals(1200, updatedUser?.nbCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
fun userExists() {
|
||||||
|
val result = userDataSource.userExists("JaneDoe")
|
||||||
|
assertTrue(result)
|
||||||
|
val resultS = userDataSource.userExists("nonexistent")
|
||||||
|
assertFalse(resultS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
fun emailExists() {
|
||||||
|
val result = userDataSource.emailExists("janedoe@example.com")
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
|
||||||
|
val resultS = userDataSource.emailExists("nonexistent@example.com")
|
||||||
|
|
||||||
|
assertFalse(resultS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
fun canHaveDailyGift() {
|
||||||
|
database.update(UsersEntity) {
|
||||||
|
set(it.lastGift, Instant.now().minusSeconds(86400 * 2)) // 2 days ago
|
||||||
|
where { it.username eq "JaneDoe" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = userDataSource.canHaveDailyGift("JaneDoe")
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
|
||||||
|
val resultS = userDataSource.canHaveDailyGift("JaneDoe")
|
||||||
|
|
||||||
|
assertFalse(resultS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
fun addImage() {
|
||||||
|
val imageBytes = "sampleImage".toByteArray()
|
||||||
|
|
||||||
|
userDataSource.addImage("11111", imageBytes)
|
||||||
|
|
||||||
|
val resultSet = database.executeWithResult(
|
||||||
|
"""
|
||||||
|
SELECT encode(image, 'base64') AS image
|
||||||
|
FROM userimage
|
||||||
|
WHERE user_id = '11111'
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNotNull(resultSet)
|
||||||
|
if (resultSet != null && resultSet.next()) {
|
||||||
|
val image = resultSet.getString("image")
|
||||||
|
assertEquals(Base64.getEncoder().encodeToString(imageBytes), image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
fun getImage() {
|
||||||
|
val result = userDataSource.getImage("11111")
|
||||||
|
|
||||||
|
assertNotNull(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
fun removeImage() {
|
||||||
|
userDataSource.removeImage("11111")
|
||||||
|
|
||||||
|
val resultSet = database.executeWithResult(
|
||||||
|
"""
|
||||||
|
SELECT encode(image, 'base64') AS image
|
||||||
|
FROM userimage
|
||||||
|
WHERE user_id = '11111'
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNotNull(resultSet)
|
||||||
|
if (resultSet != null && resultSet.next()) {
|
||||||
|
val image = resultSet.getString("image")
|
||||||
|
assertNull(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue