Compare commits

..

No commits in common. 'master' and 'V3' have entirely different histories.
master ... V3

@ -1,147 +0,0 @@
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

@ -29,6 +29,7 @@ steps:
- export SONAR_SCANNER_OPTS="-server" - 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 - sonar-scanner -D sonar.projectKey=Api-Allin -D sonar.sources=./Sources -D sonar.host.url=https://codefirst.iut.uca.fr/sonar
--- ---
kind: pipeline kind: pipeline
@ -45,6 +46,24 @@ steps:
commands: commands:
- hadolint Sources/Dockerfile - hadolint Sources/Dockerfile
- 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
- name: docker-image - name: docker-image
image: plugins/docker image: plugins/docker
settings: settings:
@ -64,9 +83,6 @@ steps:
CONTAINERNAME: api CONTAINERNAME: api
COMMAND: create COMMAND: create
OVERWRITE: true OVERWRITE: true
CODEFIRST_CLIENTDRONE_ENV_DATA_SOURCE: postgres
CODEFIRST_CLIENTDRONE_ENV_CODEFIRST_CONTAINER:
from_secret: CODEFIRST_CONTAINER
CODEFIRST_CLIENTDRONE_ENV_POSTGRES_DB: CODEFIRST_CLIENTDRONE_ENV_POSTGRES_DB:
from_secret: db_database from_secret: db_database
CODEFIRST_CLIENTDRONE_ENV_POSTGRES_USER: CODEFIRST_CLIENTDRONE_ENV_POSTGRES_USER:
@ -80,20 +96,3 @@ steps:
ADMINS: lucasevard,emrekartal,arthurvalin,lucasdelanier ADMINS: lucasevard,emrekartal,arthurvalin,lucasdelanier
depends_on: [docker-image] depends_on: [docker-image]
- 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

2
.gitignore vendored

@ -36,5 +36,3 @@ out/
.vscode/ .vscode/
!**/src/target/** !**/src/target/**
**/src/target/**
/src/target

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

@ -44,10 +44,6 @@
- Pour stocker les données des utilisateurs et des paris, nous utilisons une base de données PostgreSQL sur laquelle l'API s'appuie pour récupérer et enregistrer des données. En raison de sa conformité aux dernières normes SQL, garantissant une compatibilité et une évolutivité optimales, ainsi que de son efficacité à gérer un grand nombre de données. - Pour stocker les données des utilisateurs et des paris, nous utilisons une base de données PostgreSQL sur laquelle l'API s'appuie pour récupérer et enregistrer des données. En raison de sa conformité aux dernières normes SQL, garantissant une compatibilité et une évolutivité optimales, ainsi que de son efficacité à gérer un grand nombre de données.
</br> </br>
<img src="Documentation/Images/ktorm.png" width="50" /> **Ktorm**
- Nous avons choisi Ktorm comme ORM pour notre projet en raison de sa compatibilité native avec le framework Ktor. Ktorm permet de mapper notre modèle aux tables de la base de données PostgreSQL de manière fluide et efficace, réduisant les erreurs potentielles liées à la manipulation directe de SQL.
# Outils # Outils
Pour la partie API, nous utilisons plusieurs outils et méthodes : Pour la partie API, nous utilisons plusieurs outils et méthodes :
@ -69,10 +65,6 @@ Afin de garantir la sécurité des échanges d'informations entre notre applicat
Ils sont essentiels pour une transmission sécurisée des données entre les différentes couches de l'application. Ils sont essentiels pour une transmission sécurisée des données entre les différentes couches de l'application.
Contribuent ainsi à maintenir l'intégrité et la cohérence des données tout au long du processus. Contribuent ainsi à maintenir l'intégrité et la cohérence des données tout au long du processus.
### Mock et Base de données
En cas de problème sur le déployement et l'accès à la base de données, des mocks seront utilisables afin de simuler le comportement que pourrait avoir la base de données mais de manière temporaire. Ces mocks permettront de continuer le développement et les tests de l'application sans interruption, garantissant ainsi la productivité. Une fois que l'accès à la base de données sera rétabli, il suffira de désactiver les mocks pour revenir à l'utilisation normale de la base de données PostgreSQL.
# Controllers # Controllers
Notre API est organisée en utilisant une séparation logique des routes par le biais de controllers. Notre API est organisée en utilisant une séparation logique des routes par le biais de controllers.
@ -88,14 +80,15 @@ Le controller BET gère toutes les opérations liées aux paris. Il permet de r
:white_check_mark: Cette séparation permet une gestion plus claire et modulaire des fonctionnalités de l'API, facilitant la maintenance et l'extension de notre système. :white_check_mark: Cette séparation permet une gestion plus claire et modulaire des fonctionnalités de l'API, facilitant la maintenance et l'extension de notre système.
# Déploiement # Déploiement
Le déploiement est réalisé sur Code First via les services **Drone** et **Runner** ! :rocket: Le déploiement est réalisé sur Code First via les services **Drone** et **Runner** ! :rocket:
Lien de l'API sur codefirst : [API All In](https://codefirst.iut.uca.fr/containers/AllDev-api) Lien de l'API sur codefirst : [API All In](https://codefirst.iut.uca.fr/containers/AllDev-api)
<div align = center> <div align = center>
© AllDev - API © AllDev - API
</div> </div>

@ -8,6 +8,5 @@ EXPOSE 8080
RUN mkdir /app RUN mkdir /app
COPY --from=build /home/maven/src/target/*-with-dependencies.jar /app/ktor-docker-sample.jar COPY --from=build /home/maven/src/target/*-with-dependencies.jar /app/ktor-docker-sample.jar
ENTRYPOINT ["java", "-jar", "/app/ktor-docker-sample.jar"] ENTRYPOINT ["java", "-jar", "/app/ktor-docker-sample.jar"]

@ -10,9 +10,10 @@
<properties> <properties>
<kotlin.version>1.9.10</kotlin.version> <kotlin.version>1.9.10</kotlin.version>
<serialization.version>1.5.0</serialization.version> <serialization.version>1.5.0</serialization.version>
<ktor_version>2.3.11</ktor_version> <ktor_version>2.3.4</ktor_version>
<kotlin.code.style>official</kotlin.code.style> <kotlin.code.style>official</kotlin.code.style>
<logback_version>1.4.14</logback_version> <kotlin_version>1.9.10</kotlin_version>
<logback_version>1.4.11</logback_version>
<slf4j_version>2.0.9</slf4j_version> <slf4j_version>2.0.9</slf4j_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental> <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
@ -34,33 +35,18 @@
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.7.3</version> <version>42.7.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.ktor</groupId> <groupId>io.ktor</groupId>
<artifactId>ktor-server-netty-jvm</artifactId> <artifactId>ktor-server-netty-jvm</artifactId>
<version>${ktor_version}</version> <version>${ktor_version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.github.smiley4</groupId>
<artifactId>ktor-swagger-ui</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-swagger-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.ktorm</groupId> <groupId>org.ktorm</groupId>
<artifactId>ktorm-core</artifactId> <artifactId>ktorm-core</artifactId>
<version>3.2.0</version> <version>3.2.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.ktorm</groupId>
<artifactId>ktorm-support-postgresql</artifactId>
<version>3.2.0</version>
</dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
@ -110,7 +96,7 @@
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId> <artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version> <version>${kotlin_version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -129,11 +115,6 @@
<artifactId>ktor-server-auth-jvm</artifactId> <artifactId>ktor-server-auth-jvm</artifactId>
<version>2.3.4</version> <version>2.3.4</version>
</dependency> </dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
@ -142,24 +123,7 @@
<dependency> <dependency>
<groupId>io.ktor</groupId> <groupId>io.ktor</groupId>
<artifactId>ktor-server-auth-jwt-jvm</artifactId> <artifactId>ktor-server-auth-jwt-jvm</artifactId>
<version>2.3.11</version> <version>2.3.4</version>
</dependency>
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-generators</artifactId>
<version>1.0.50</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
@ -174,9 +138,9 @@
<plugin> <plugin>
<artifactId>kotlin-maven-plugin</artifactId> <artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version> <version>${kotlin_version}</version>
<configuration> <configuration>
<jvmTarget>11</jvmTarget> <jvmTarget>1.8</jvmTarget>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

@ -1,15 +1,9 @@
package allin package allin
import allin.data.AllInDataSource import allin.entities.*
import allin.data.mock.MockDataSource
import allin.data.postgres.PostgresDataSource
import allin.routing.* import allin.routing.*
import allin.utils.TokenManager import allin.utils.*
import allin.utils.TokenManager.Companion.Claims.USERNAME
import allin.utils.kronJob
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import io.github.smiley4.ktorswaggerui.SwaggerUI
import io.github.smiley4.ktorswaggerui.data.SwaggerUiSort
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
@ -18,34 +12,21 @@ import io.ktor.server.config.*
import io.ktor.server.engine.* import io.ktor.server.engine.*
import io.ktor.server.netty.* import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.contentnegotiation.*
import java.time.ZonedDateTime import org.ktorm.database.Database
import kotlin.time.Duration.Companion.minutes
import kotlin.time.ExperimentalTime
@ExperimentalTime val db_database=System.getenv().get("POSTGRES_DB")
val BET_VERIFY_DELAY = 1.minutes val db_user=System.getenv().get("POSTGRES_USER")
val db_password=System.getenv().get("POSTGRES_PASSWORD")
val db_host=System.getenv().get("POSTGRES_HOST")
val data_source = System.getenv()["DATA_SOURCE"] val database = Database.connect("jdbc:postgresql://$db_host/$db_database", user = db_user, password = db_password)
val isCodeFirstContainer = System.getenv()["CODEFIRST_CONTAINER"].orEmpty()
val hostIP = "0.0.0.0"
val hostPort = 8080
private val allInDataSource: AllInDataSource = when (data_source) {
"mock" -> MockDataSource()
"postgres" -> PostgresDataSource()
else -> MockDataSource()
}
val Application.dataSource: AllInDataSource
get() = allInDataSource
fun main() { fun main() {
embeddedServer(Netty, port = hostPort, host = hostIP) { embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
extracted() extracted()
}.start(wait = true) }.start(wait = true)
} }
@OptIn(ExperimentalTime::class)
private fun Application.extracted() { private fun Application.extracted() {
val config = HoconApplicationConfig(ConfigFactory.load()) val config = HoconApplicationConfig(ConfigFactory.load())
val tokenManager = TokenManager.getInstance(config) val tokenManager = TokenManager.getInstance(config)
@ -54,45 +35,22 @@ private fun Application.extracted() {
verifier(tokenManager.verifyJWTToken()) verifier(tokenManager.verifyJWTToken())
realm = config.property("realm").getString() realm = config.property("realm").getString()
validate { jwtCredential -> validate { jwtCredential ->
if (jwtCredential.payload.getClaim(USERNAME).asString().isNotEmpty()) if (jwtCredential.payload.getClaim("username").asString().isNotEmpty())
JWTPrincipal(jwtCredential.payload) JWTPrincipal(jwtCredential.payload)
else null else null
} }
} }
} }
install(ContentNegotiation) {
json()
install(ContentNegotiation) { json() }
install(SwaggerUI) {
swagger {
swaggerUrl = "swagger"
rootHostPath = isCodeFirstContainer
swaggerUrl = "$isCodeFirstContainer/swagger"
onlineSpecValidator()
displayOperationId = true
showTagFilterInput = true
sort = SwaggerUiSort.HTTP_METHOD
}
info {
title = "Allin API"
version = "latest"
description = "Allin API"
license {
name = "Apache 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.html"
}
}
}
basicRouter()
userRouter()
betRouter()
participationRouter()
betDetailRouter()
friendRouter()
kronJob(BET_VERIFY_DELAY) {
dataSource.betDataSource.updateBetStatuses(ZonedDateTime.now())
} }
BasicRouting()
UserRouter()
BetRouter()
ParticipationRouter()
BetDetailRouter()
UsersEntity.createUserTable()
BetsEntity.createBetsTable()
ResponsesEntity.createResponseTable()
ParticipationsEntity.createParticipationTable()
} }

@ -1,8 +0,0 @@
package allin.data
abstract class AllInDataSource {
abstract val userDataSource: UserDataSource
abstract val betDataSource: BetDataSource
abstract val participationDataSource: ParticipationDataSource
abstract val friendDataSource: FriendDataSource
}

@ -1,26 +0,0 @@
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)
}

@ -1,20 +0,0 @@
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
}

@ -1,9 +0,0 @@
package allin.data
import allin.model.Participation
interface ParticipationDataSource {
fun addParticipation(participation: Participation)
fun getParticipationFromBetId(betid: String): List<Participation>
fun deleteParticipation(id: String): Boolean
}

@ -1,19 +0,0 @@
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?
}

@ -1,254 +0,0 @@
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))
}
}
}

@ -1,32 +0,0 @@
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) }
}

@ -1,51 +0,0 @@
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))
}
}

@ -1,88 +0,0 @@
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
}
}

@ -1,115 +0,0 @@
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,
)
}
}

@ -1,296 +0,0 @@
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
})
}
}
}

@ -1,164 +0,0 @@
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) }
}

@ -1,97 +0,0 @@
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))
}
}
}
}

@ -1,68 +0,0 @@
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
}
}

@ -1,112 +0,0 @@
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
}
}

@ -1,36 +0,0 @@
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)

@ -1,98 +0,0 @@
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)

@ -1,25 +0,0 @@
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)

@ -1,51 +0,0 @@
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)

@ -1,21 +0,0 @@
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)

@ -1,28 +0,0 @@
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)

@ -1,43 +0,0 @@
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)

@ -1,21 +0,0 @@
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)

@ -1,21 +0,0 @@
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)

@ -1,62 +0,0 @@
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)

@ -1,29 +0,0 @@
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,18 +1,4 @@
package allin.dto package allin.dto
import allin.model.FriendStatus
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class UserDTO( data class UserDTO(val id: String, val username: String, val email: String, val nbCoins: Int, var token:String?)
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?
)

@ -0,0 +1,65 @@
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))
}
}

@ -0,0 +1,90 @@
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
}
}

@ -0,0 +1,41 @@
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)
}
}
}
}

@ -0,0 +1,84 @@
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
}
}

@ -1,74 +0,0 @@
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,9 +1,8 @@
package allin.ext package allin.ext
import allin.data.UserDataSource
import allin.dto.UserDTO import allin.dto.UserDTO
import allin.entities.UsersEntity
import allin.model.ApiMessage import allin.model.ApiMessage
import allin.utils.TokenManager.Companion.Claims.USERNAME
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
@ -15,12 +14,11 @@ suspend fun PipelineContext<*, ApplicationCall>.hasToken(content: suspend (princ
call.principal<JWTPrincipal>()?.let { content(it) } ?: call.respond(HttpStatusCode.Unauthorized) call.principal<JWTPrincipal>()?.let { content(it) } ?: call.respond(HttpStatusCode.Unauthorized)
suspend fun PipelineContext<*, ApplicationCall>.verifyUserFromToken( suspend fun PipelineContext<*, ApplicationCall>.verifyUserFromToken(
userDataSource: UserDataSource,
principal: JWTPrincipal, principal: JWTPrincipal,
content: suspend (user: UserDTO, password: String) -> Unit content: suspend (user: UserDTO, password: String) -> Unit
) { ) {
val username = principal.payload.getClaim(USERNAME).asString() val username = principal.payload.getClaim("username").asString()
val user = userDataSource.getUserByUsername(username) val userPassword = UsersEntity.getUserByUsernameAndPassword(username)
user.first?.let { content(it, user.second ?: "") } userPassword.first?.let { content(it, userPassword.second ?: "") }
?: call.respond(HttpStatusCode.NotFound, ApiMessage.TOKEN_USER_NOT_FOUND) ?: call.respond(HttpStatusCode.NotFound, ApiMessage.TokenUserNotFound)
} }

@ -1,27 +1,14 @@
package allin.model package allin.model
object ApiMessage { object ApiMessage {
const val WELCOME = "Welcome on AllIn's API !" const val Welcome = "Welcome on AllIn's API !"
const val TOKEN_USER_NOT_FOUND = "User not found with the valid token !" const val TokenUserNotFound = "User not found with the valid token !"
const val USER_NOT_FOUND = "User not found." const val UserNotFound = "User not found."
const val BET_NOT_FOUND = "Bet not found." const val BetNotFound = "Bet not found."
const val BET_ALREADY_EXIST = "Bet already exists." const val BetAlreadyExist = "Bet already exists."
const val INCORRECT_LOGIN_PASSWORD = "Login and/or password incorrect." const val IncorrectLoginPassword = "Login and/or password incorrect."
const val USER_ALREADY_EXISTS = "Username already exists." const val UserAlreadyExist = "Mail and/or username already exists."
const val MAIL_ALREADY_EXISTS = "Mail already exists." const val InvalidMail = "Invalid mail."
const val INVALID_MAIL = "Invalid mail." const val ParticipationNotFound = "Participation not found."
const val PARTICIPATION_NOT_FOUND = "Participation not found." const val NotEnoughCoins = "Not enough coins."
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"
} }

@ -1,29 +1,21 @@
package allin.model package allin.model
import allin.model.BetStatus.IN_PROGRESS import allin.serializer.UUIDSerializer
import allin.serializer.ZonedDateTimeSerializer import allin.serializer.ZonedDateTimeSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.*
const val YES_VALUE = "Yes"
const val NO_VALUE = "No"
@Serializable @Serializable
data class Bet( data class Bet(
val id: String = "", val id: String = "",
val theme: String, val theme: String,
val sentenceBet: String, val sentenceBet: String,
val status: BetStatus = IN_PROGRESS,
val type: BetType,
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean, var isPrivate: Boolean,
var response: List<String>, var response: MutableList<String>,
val createdBy: String = "", val createdBy: String = ""
var popularityscore: Int = 0,
val totalStakes: Int = 0,
val totalParticipants: Int = 0,
val userInvited: List<String>? = null
) )
@Serializable @Serializable
@ -31,17 +23,5 @@ data class UpdatedBetData(
val id: String, val id: String,
@Serializable(ZonedDateTimeSerializer::class) val endBet: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) val endBet: ZonedDateTime,
val isPrivate: Boolean, val isPrivate: Boolean,
val response: List<String> val response: MutableList<String>
)
@Serializable
data class InvitationBet(
val betid: String,
val userId: String
)
@Serializable
data class UpdatedPrivateBet(
val betid: String,
val usersInvited: List<String>? = null
) )

@ -16,25 +16,19 @@ data class BetDetail(
val bet: Bet, // Le Bet val bet: Bet, // Le Bet
val answers: List<BetAnswerDetail>?, // Pour chaque réponse possible du bet les détails val answers: List<BetAnswerDetail>?, // Pour chaque réponse possible du bet les détails
val participations: List<Participation>?, // La liste des participations val participations: List<Participation>?, // La liste des participations
val userParticipation: Participation?, // La participation du User current val userParticipation: Participation? // La participation du User current
val wonParticipation: Participation? // La participation gagnante
) )
fun getBetAnswerDetail( fun getBetAnswerDetail(bet: Bet, participations: List<Participation>): List<BetAnswerDetail> {
bet: Bet,
participations: List<Participation>,
infos: List<BetAnswerInfo>
): List<BetAnswerDetail> {
return bet.response.map { response -> return bet.response.map { response ->
val responseParticipations = participations.filter { it.answer == response } val responseParticipations = participations.filter { it.answer == response }
val answerInfo = infos.find { it.response == response }
BetAnswerDetail( BetAnswerDetail(
response = response, response = response,
totalStakes = answerInfo?.totalStakes ?: 0, totalStakes = responseParticipations.sumOf { it.stake },
totalParticipants = responseParticipations.size, totalParticipants = responseParticipations.size,
highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0, highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0,
odds = answerInfo?.odds ?: 1f odds = if (participations.isEmpty()) 1f else responseParticipations.size / participations.size.toFloat()
) )
} }
} }

@ -1,15 +0,0 @@
package allin.model
import kotlinx.serialization.Serializable
enum class BetFilter {
PUBLIC,
INVITATION,
IN_PROGRESS,
FINISHED
}
@Serializable
data class BetFiltersRequest(
val filters: List<BetFilter>
)

@ -1,18 +0,0 @@
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
)

@ -1,9 +0,0 @@
package allin.model
import kotlinx.serialization.Serializable
@Serializable
data class BetResult(
val betId: String,
val result: String
)

@ -1,12 +0,0 @@
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
)

@ -1,9 +0,0 @@
package allin.model
enum class BetStatus {
IN_PROGRESS,
WAITING,
CLOSING,
FINISHED,
CANCELLED
}

@ -1,7 +0,0 @@
package allin.model
enum class BetType {
MATCH,
BINARY,
CUSTOM
}

@ -1,7 +0,0 @@
package allin.model
data class Friend(
val sender: String,
val receiver: String
)

@ -1,7 +0,0 @@
package allin.model
enum class FriendStatus {
FRIEND,
REQUESTED,
NOT_FRIEND
}

@ -6,11 +6,9 @@ import kotlinx.serialization.Serializable
data class Participation( data class Participation(
val id: String, val id: String,
val betId: String, val betId: String,
val userId: String,
val answer: String,
val stake: Int,
val username: String, val username: String,
val imageUser: String? = null val answer: String,
val stake: Int
) )
@Serializable @Serializable

@ -1,11 +1,7 @@
package allin.model package allin.model
import allin.dto.UserDTO
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
const val DEFAULT_COIN_AMOUNT = 500
const val DAILY_GIFT_MIN = 10
const val DAILY_GIFT_MAX = 150
@Serializable @Serializable
data class User( data class User(
@ -13,30 +9,10 @@ data class User(
val username: String, val username: String,
val email: String, val email: String,
var password: String, var password: String,
var nbCoins: Int = DEFAULT_COIN_AMOUNT, var nbCoins: Int = 500,
var token: String? = null, var token: String? = null
var image: String? = null,
var bestWin: Int?,
var nbBets: Int,
var nbFriends: Int,
) )
{
fun toDto(friendStatus: FriendStatus? = null) =
UserDTO(
id = id,
username = username,
email = email,
nbCoins = nbCoins,
token = token,
friendStatus = friendStatus,
image = image,
bestWin = bestWin?:0,
nbBets = nbBets,
nbFriends=nbFriends
)
}
@Serializable @Serializable
data class UserRequest( data class UserRequest(
val username: String, val username: String,
@ -49,9 +25,3 @@ data class CheckUser(
val login: String, val login: String,
val password: String val password: String
) )
@Serializable
data class UserImage(
val id: String,
val image: ByteArray,
)

@ -0,0 +1,13 @@
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!")
}
}
}

@ -0,0 +1,44 @@
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")
}
}
}
}
}
}
}

@ -0,0 +1,111 @@
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)
}
}
}
}
}
}

@ -0,0 +1,58 @@
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)
}
}
}
}
}

@ -0,0 +1,98 @@
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)
}
}
}
}
}
}

@ -1,31 +0,0 @@
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)
}
}
}

@ -1,64 +0,0 @@
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)
}
}
}
}
}
}
}

@ -1,419 +0,0 @@
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)
}
}
}
}
}
}

@ -1,218 +0,0 @@
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)
}
}
}
}
}
}

@ -1,115 +0,0 @@
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)
}
}
}
}
}
}

@ -1,289 +0,0 @@
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,6 +1,5 @@
package allin.serializer package allin.serializer
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveKind
@ -8,11 +7,13 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import java.sql.Timestamp
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@OptIn(ExperimentalSerializationApi::class)
@Serializer(ZonedDateTime::class) @Serializer(ZonedDateTime::class)
object ZonedDateTimeSerializer : KSerializer<ZonedDateTime> { object ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
private val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z") private val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z")

@ -1,14 +1,11 @@
package allin.utils package allin.utils
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import io.ktor.server.config.* import io.ktor.server.config.HoconApplicationConfig
object AppConfig { object AppConfig {
private val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load()) val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
val tokenManager = TokenManager.getInstance(config) val tokenManager = TokenManager.getInstance(config)
val regexChecker = RegexChecker() val regexChecker= RegexChecker()
val cryptManager = CryptManager() val cryptManager = CryptManager()
val imageManager = ImageManager()
val urlManager = URLManager()
val logManager = LogManager()
} }

@ -4,12 +4,9 @@ import allin.model.User
import org.mindrot.jbcrypt.BCrypt import org.mindrot.jbcrypt.BCrypt
class CryptManager { class CryptManager {
private val salt = addDollarsSecrets( val salt=addDollarsSecrets(System.getenv().get("SALT").toString())
System.getenv()["SALT"] ?: throw RuntimeException("Salt is null")
)
// Cette fonction permet de remettre les $ que drone supprime dans les secrets drone // Cette fonction permet de remettre les $ que drone supprime dans les secrets drone
private fun addDollarsSecrets(chaine: String): String { fun addDollarsSecrets(chaine: String): String {
val stringBuilder = StringBuilder(chaine) val stringBuilder = StringBuilder(chaine)
stringBuilder.insert(0, '$') stringBuilder.insert(0, '$')
stringBuilder.insert(3, '$') stringBuilder.insert(3, '$')
@ -17,20 +14,17 @@ class CryptManager {
return stringBuilder.toString() return stringBuilder.toString()
} }
fun passwordCrypt(password : String): String {
fun passwordCrypt(password: String): String { return BCrypt.hashpw(password,salt)
return BCrypt.hashpw(password, salt)
} }
fun passwordCrypt(user: User){
fun passwordCrypt(user: User) { user.password=BCrypt.hashpw(user.password,salt)
user.password = BCrypt.hashpw(user.password, salt)
} }
fun passwordDecrypt(password: String, passwordClear: String): Boolean{
fun passwordDecrypt(password: String, passwordClear: String): Boolean { return BCrypt.hashpw(passwordClear,salt)==password
return BCrypt.hashpw(passwordClear, salt) == password
} }
fun checkPassword(hashed: String, clear: String): Boolean { fun CheckPassword(hashed: String, clear: String): Boolean{
return BCrypt.checkpw(hashed, clear) return BCrypt.checkpw(hashed,clear)
} }
} }

@ -0,0 +1,13 @@
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()
}
}

@ -1,40 +0,0 @@
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", "")
}

@ -1,12 +0,0 @@
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,10 +1,13 @@
package allin.utils package allin.utils
class RegexChecker { class RegexChecker {
private val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
private val emailRegex="^[A-Za-z0-9+_.-]+@(.+)$"
fun isEmailInvalid(email: String): Boolean { fun isEmailInvalid(email: String): Boolean {
val emailRegex = Regex(emailRegex) val emailRegex = Regex(emailRegex)
return !emailRegex.matches(email) return !emailRegex.matches(email)
} }
} }

@ -2,26 +2,26 @@ package allin.utils
import allin.dto.UserDTO import allin.dto.UserDTO
import allin.model.User import allin.model.User
import allin.utils.TokenManager.Companion.Claims.USERNAME
import com.auth0.jwt.JWT import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.DecodedJWT
import io.ktor.server.auth.jwt.* import io.ktor.server.auth.jwt.*
import io.ktor.server.config.* import io.ktor.server.config.*
import java.util.* import java.util.*
class TokenManager private constructor(config: HoconApplicationConfig) { class TokenManager private constructor(val config: HoconApplicationConfig) {
private val audience = config.property("audience").getString() val audience = config.property("audience").getString()
private val secret = config.property("secret").getString() val secret = config.property("secret").getString()
private val issuer = config.property("issuer").getString() val issuer = config.property("issuer").getString()
private fun generateJWTToken(user: User): String { fun generateJWTToken(user: User): String {
val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde
return JWT.create() return JWT.create()
.withAudience(audience) .withAudience(audience)
.withIssuer(issuer) .withIssuer(issuer)
.withClaim(USERNAME, user.username) .withClaim("username", user.username)
.withExpiresAt(Date(expirationDate)) .withExpiresAt(Date(expirationDate))
.sign(Algorithm.HMAC256(secret)) .sign(Algorithm.HMAC256(secret))
} }
@ -52,34 +52,30 @@ class TokenManager private constructor(config: HoconApplicationConfig) {
} }
} }
private fun generateJWTToken(user: UserDTO): String { fun generateJWTToken(user: UserDTO): String {
val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde
return JWT.create() return JWT.create()
.withAudience(audience) .withAudience(audience)
.withIssuer(issuer) .withIssuer(issuer)
.withClaim(USERNAME, user.username) .withClaim("username", user.username)
.withExpiresAt(Date(expirationDate)) .withExpiresAt(Date(expirationDate))
.sign(Algorithm.HMAC256(secret)) .sign(Algorithm.HMAC256(secret))
} }
private fun isTokenExpired(token: String): Boolean { fun isTokenExpired(token: String): Boolean {
val expirationTime = JWT.decode(token).expiresAt.time val expirationTime = JWT.decode(token).expiresAt.time
return System.currentTimeMillis() > expirationTime return System.currentTimeMillis() > expirationTime
} }
private fun getUserToken(user: User): String? = user.token fun getUserToken(user: User): String? = user.token
private fun getUserToken(user: UserDTO): String? = user.token fun getUserToken(user: UserDTO): String? = user.token
fun getUsernameFromToken(principal: JWTPrincipal): String { fun getUsernameFromToken(principal: JWTPrincipal): String {
return principal.payload.getClaim(USERNAME).asString() return principal.payload.getClaim("username").asString()
} }
companion object { companion object {
object Claims {
const val USERNAME = "username"
}
private var instance: TokenManager? = null private var instance: TokenManager? = null
fun getInstance(config: HoconApplicationConfig): TokenManager { fun getInstance(config: HoconApplicationConfig): TokenManager {
return instance ?: synchronized(this) { return instance ?: synchronized(this) {

@ -1,13 +0,0 @@
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}"
}
}

@ -1,15 +0,0 @@
package allin.utils
import kotlinx.coroutines.*
import kotlin.time.Duration
@OptIn(DelicateCoroutinesApi::class)
fun kronJob(duration: Duration, action: () -> Unit) =
GlobalScope.launch {
withContext(Dispatchers.IO) {
while (true) {
runCatching { action() }
delay(duration.inWholeMilliseconds)
}
}
}

@ -1,4 +1,4 @@
secret="secret" secret="secret"
issuer="http://0.0.0.0:8080/" issuer="http://0.0.0.0:8080/"
audience="http://0.0.0.0:8080/" audience="http://0.0.0.0:8080/"
realm="allin" realm="Access to main page"

@ -1,6 +1,4 @@
package allin package allin
class ApplicationTest { class ApplicationTest {
} }

@ -1,129 +0,0 @@
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)
}
}

@ -1,229 +0,0 @@
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…
Cancel
Save