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
|
||||
|
||||
import allin.model.FriendStatus
|
||||
import kotlinx.serialization.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
|
||||
|
||||
object ApiMessage {
|
||||
const val Welcome = "Welcome on AllIn's API !"
|
||||
const val TokenUserNotFound = "User not found with the valid token !"
|
||||
const val UserNotFound = "User not found."
|
||||
const val BetNotFound = "Bet not found."
|
||||
const val BetAlreadyExist = "Bet already exists."
|
||||
const val IncorrectLoginPassword = "Login and/or password incorrect."
|
||||
const val UserAlreadyExist = "Mail and/or username already exists."
|
||||
const val InvalidMail = "Invalid mail."
|
||||
const val ParticipationNotFound = "Participation not found."
|
||||
const val NotEnoughCoins = "Not enough coins."
|
||||
const val WELCOME = "Welcome on AllIn's API !"
|
||||
const val TOKEN_USER_NOT_FOUND = "User not found with the valid token !"
|
||||
const val USER_NOT_FOUND = "User not found."
|
||||
const val BET_NOT_FOUND = "Bet not found."
|
||||
const val BET_ALREADY_EXIST = "Bet already exists."
|
||||
const val INCORRECT_LOGIN_PASSWORD = "Login and/or password incorrect."
|
||||
const val USER_ALREADY_EXISTS = "Username already exists."
|
||||
const val MAIL_ALREADY_EXISTS = "Mail already exists."
|
||||
const val INVALID_MAIL = "Invalid mail."
|
||||
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
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import io.ktor.server.config.HoconApplicationConfig
|
||||
import io.ktor.server.config.*
|
||||
|
||||
object AppConfig {
|
||||
val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
|
||||
private val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
|
||||
val tokenManager = TokenManager.getInstance(config)
|
||||
val regexChecker= RegexChecker()
|
||||
val regexChecker = RegexChecker()
|
||||
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
|
||||
|
||||
class RegexChecker {
|
||||
|
||||
private val emailRegex="^[A-Za-z0-9+_.-]+@(.+)$"
|
||||
|
||||
private val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
|
||||
|
||||
fun isEmailInvalid(email: String): Boolean {
|
||||
val emailRegex = Regex(emailRegex)
|
||||
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"
|
||||
issuer="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
|
||||
|
||||
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