diff --git a/.gitignore b/.gitignore index f0d8881..c426c32 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,4 @@ out/ /.nb-gradle/ ### VS Code ### -.vscode/ - -**/src/target/** \ No newline at end of file +.vscode/ \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/Application.kt b/Sources/src/main/kotlin/allin/Application.kt index 657f576..93119eb 100644 --- a/Sources/src/main/kotlin/allin/Application.kt +++ b/Sources/src/main/kotlin/allin/Application.kt @@ -1,19 +1,19 @@ package allin +import allin.model.User import allin.routing.BasicRouting import allin.routing.BetRouter -import allin.routing.ParticipationRouter import allin.routing.UserRouter -import allin.utils.TokenManager import com.typesafe.config.ConfigFactory import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.auth.jwt.* import io.ktor.server.config.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* +import allin.utils.TokenManager +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { @@ -27,9 +27,9 @@ private fun Application.extracted() { authentication { jwt { verifier(tokenManager.verifyJWTToken()) - realm = config.property("realm").getString() + realm=config.property("realm").getString() validate { jwtCredential -> - if (jwtCredential.payload.getClaim("username").asString().isNotEmpty()) + if(jwtCredential.payload.getClaim("username").asString().isNotEmpty()) JWTPrincipal(jwtCredential.payload) else null } @@ -41,5 +41,4 @@ private fun Application.extracted() { BasicRouting() UserRouter() BetRouter() - ParticipationRouter() } diff --git a/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt b/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt deleted file mode 100644 index 0d9cfd9..0000000 --- a/Sources/src/main/kotlin/allin/ext/PipelineContextExt.kt +++ /dev/null @@ -1,23 +0,0 @@ -package allin.ext - -import allin.model.ApiMessage -import allin.model.User -import allin.routing.users -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.util.pipeline.* - -suspend fun PipelineContext<*, ApplicationCall>.hasToken(content: suspend (principal: JWTPrincipal) -> Unit) = - call.principal()?.let { content(it) } ?: call.respond(HttpStatusCode.Unauthorized) - -suspend fun PipelineContext<*, ApplicationCall>.verifyUserFromToken( - principal: JWTPrincipal, - content: suspend (user: User) -> Unit -) { - val username = principal.payload.getClaim("username").asString() - users.find { it.username == username }?.let { content(it) } - ?: call.respond(HttpStatusCode.NotFound, ApiMessage.TokenUserNotFound) -} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/ApiMessage.kt b/Sources/src/main/kotlin/allin/model/ApiMessage.kt deleted file mode 100644 index deaa55f..0000000 --- a/Sources/src/main/kotlin/allin/model/ApiMessage.kt +++ /dev/null @@ -1,13 +0,0 @@ -package allin.model - -object ApiMessage { - const val Welcome = "Welcome on AllIn's API !" - const val TokenUserNotFound = "User not found with the valid token !" - const val BetNotFound = "Bet not found." - const val BetAlreadyExist = "Bet already exists." - const val IncorrectLoginPassword = "Login and/or password incorrect." - const val UserAlreadyExist = "Mail and/or username already exists." - const val InvalidMail = "Invalid mail." - const val ParticipationNotFound = "Participation not found." - const val NotEnoughCoins = "Not enough coins." -} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/Bet.kt b/Sources/src/main/kotlin/allin/model/Bet.kt index cb895e1..5d4bc13 100644 --- a/Sources/src/main/kotlin/allin/model/Bet.kt +++ b/Sources/src/main/kotlin/allin/model/Bet.kt @@ -1,37 +1,19 @@ package allin.model -import allin.serializer.ZonedDateTimeSerializer +import allin.dto.UserDTOWithToken +import allin.serializer.DateSerializer import kotlinx.serialization.Serializable -import java.time.ZonedDateTime import java.util.* @Serializable -data class Bet( - val id: String, - val theme: String, - val sentenceBet: String, - @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime, - @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, - var isPrivate: Boolean, - var response: MutableList, - val createdBy: String -) +data class Bet(val id: Int, val theme: String, val sentenceBet: String, @Serializable(DateSerializer::class) val endRegistration: Date, @Serializable(DateSerializer::class) var endBet : Date, var isPrivate : Boolean, var response : MutableList, val createdBy : String) @Serializable -data class UpdatedBetData( - val id: String, - @Serializable(ZonedDateTimeSerializer::class) val endBet: ZonedDateTime, - val isPrivate: Boolean, - val response: MutableList -) +data class UpdatedBetData(val id: Int,@Serializable(DateSerializer::class) val endBet: Date, val isPrivate: Boolean, val response: MutableList) @Serializable -data class BetWithoutId( - val theme: String, - val sentenceBet: String, - @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime, - @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, - var isPrivate: Boolean, - var response: MutableList, - val createdBy: String -) +data class BetWithoutId(val theme: String, val sentenceBet: String, @Serializable(DateSerializer::class) val endRegistration: Date, @Serializable(DateSerializer::class) var endBet : Date, var isPrivate : Boolean, var response : MutableList, val createdBy : String) + +fun convertBetWithoutIdToBet(betWithoutId: BetWithoutId,id : Int, username : String): Bet { + return Bet(id,betWithoutId.theme,betWithoutId.sentenceBet,betWithoutId.endRegistration, betWithoutId.endBet, betWithoutId.isPrivate, betWithoutId.response, username) +} diff --git a/Sources/src/main/kotlin/allin/model/BetAction.kt b/Sources/src/main/kotlin/allin/model/BetAction.kt new file mode 100644 index 0000000..f830e2c --- /dev/null +++ b/Sources/src/main/kotlin/allin/model/BetAction.kt @@ -0,0 +1,5 @@ +package allin.model + +import allin.dto.UserDTOWithToken +data class BetAction(val id:Int, val coins: Int, val user: String, val bet: Int) +data class BetActionCompleted(val id:Int, val coins: Int, val user: UserDTOWithToken, val bet: Bet) diff --git a/Sources/src/main/kotlin/allin/model/Participation.kt b/Sources/src/main/kotlin/allin/model/Participation.kt deleted file mode 100644 index b2e8495..0000000 --- a/Sources/src/main/kotlin/allin/model/Participation.kt +++ /dev/null @@ -1,19 +0,0 @@ -package allin.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Participation( - val id: String, - val betId: String, - val username: String, - val answer: String, - val stake: Int -) - -@Serializable -data class ParticipationRequest( - val betId: String, - val answer: String, - val stake: Int -) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/User.kt b/Sources/src/main/kotlin/allin/model/User.kt index 6234988..d01dc50 100644 --- a/Sources/src/main/kotlin/allin/model/User.kt +++ b/Sources/src/main/kotlin/allin/model/User.kt @@ -1,26 +1,10 @@ package allin.model +import allin.dto.UserDTO import kotlinx.serialization.Serializable @Serializable -data class User( - val id: String, - val username: String, - val email: String, - var password: String, - var nbCoins: Int = 1000, - var token: String? = null -) +data class User(val username: String, val email: String, var password: String, var nbCoins: Int = 1000, var token: String? = null) @Serializable -data class UserRequest( - val username: String, - val email: String, - var password: String -) - -@Serializable -data class CheckUser( - val login: String, - val password: String -) +data class CheckUser(val login: String,val password: String) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/BasicRouting.kt b/Sources/src/main/kotlin/allin/routing/BasicRouting.kt index af603ab..0d78278 100644 --- a/Sources/src/main/kotlin/allin/routing/BasicRouting.kt +++ b/Sources/src/main/kotlin/allin/routing/BasicRouting.kt @@ -1,14 +1,13 @@ package allin.routing -import allin.model.ApiMessage import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Application.BasicRouting() { +fun Application.BasicRouting(){ routing { get("/") { - call.respond(ApiMessage.Welcome) + call.respond("Bienvenue sur l'API de AlLin!") } } } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/BetActionRouter.kt b/Sources/src/main/kotlin/allin/routing/BetActionRouter.kt new file mode 100644 index 0000000..aa68a74 --- /dev/null +++ b/Sources/src/main/kotlin/allin/routing/BetActionRouter.kt @@ -0,0 +1,13 @@ +package allin.routing + +import allin.model.BetAction +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.routing.* + +fun Application.BetActionRouter(){ + routing { + route("/BetAction/add"){ + } + } +} diff --git a/Sources/src/main/kotlin/allin/routing/BetRouter.kt b/Sources/src/main/kotlin/allin/routing/BetRouter.kt index f55abde..997cee5 100644 --- a/Sources/src/main/kotlin/allin/routing/BetRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/BetRouter.kt @@ -1,85 +1,65 @@ package allin.routing - -import allin.ext.hasToken -import allin.ext.verifyUserFromToken -import allin.model.ApiMessage -import allin.model.Bet -import allin.model.BetWithoutId -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.* +import allin.model.* +import allin.utils.AppConfig +import io.ktor.http.* +import io.ktor.server.response.* val bets = mutableListOf() -val tokenManagerBet = AppConfig.tokenManager +val tokenManagerBet= AppConfig.tokenManager -fun Application.BetRouter() { - routing { - route("/bets/add") { - post { +fun CreateId() : Int{ + return bets.size +} + +fun Application.BetRouter(){ + routing{ + route("/bets/add"){ + post{ val bet = call.receive() - val id = UUID.randomUUID().toString() + val id = CreateId() val username = tokenManagerBet.getUsernameFromToken(bet.createdBy) - 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 - ) + val findbet = bets.find { it.id == id } + if(findbet==null){ + val betWithId = convertBetWithoutIdToBet(bet,id,username) bets.add(betWithId) call.respond(HttpStatusCode.Created, betWithId) } + call.respond(HttpStatusCode.Conflict,"Bet already exist") } } - route("/bets/gets") { - get { - // if(bets.size>0) - call.respond(HttpStatusCode.Accepted, bets.toList()) - // else call.respond(HttpStatusCode.NoContent) + route("/bets/gets"){ + get{ + // if(bets.size>0) + call.respond(HttpStatusCode.Accepted, bets.toList()) + // else call.respond(HttpStatusCode.NoContent) } } - route("/bets/delete") { - post { - val idbet = call.receive>()["id"] - bets.find { it.id == idbet }?.let { findbet -> - bets.remove(findbet) - call.respond(HttpStatusCode.Accepted, findbet) - } ?: call.respond(HttpStatusCode.NotFound, ApiMessage.BetNotFound) + route("/bets/delete"){ + post{ + val idbet = call.receive>()["id"] + val findbet = bets.find { it.id == idbet } + if(findbet==null){ + call.respond(HttpStatusCode.NotFound, "Bet doesnt find") + } + bets.remove(findbet) + findbet as Bet + call.respond(HttpStatusCode.Accepted, findbet) } } - route("bets/update") { - post { + route("bets/update"){ + post{ val updatedBetData = call.receive() - bets.find { it.id == updatedBetData.id }?.let { findbet -> + val findbet = bets.find { it.id == updatedBetData.id } + if (findbet == null) { + call.respond(HttpStatusCode.NotFound, "Bet not found") + } else { 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") { - hasToken { principal -> - verifyUserFromToken(principal) { user -> - val bets = participations - .filter { it.username == user.username } - .mapNotNull { itParticipation -> bets.find { it.id == itParticipation.betId } } - call.respond(HttpStatusCode.OK, bets) - } } } } diff --git a/Sources/src/main/kotlin/allin/routing/ParticipationRouter.kt b/Sources/src/main/kotlin/allin/routing/ParticipationRouter.kt deleted file mode 100644 index 11ac4da..0000000 --- a/Sources/src/main/kotlin/allin/routing/ParticipationRouter.kt +++ /dev/null @@ -1,56 +0,0 @@ -package allin.routing - -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.* - -val participations = mutableListOf() - -fun Application.ParticipationRouter() { - routing { - authenticate { - post("/participations/add") { - hasToken { principal -> - val participation = call.receive() - verifyUserFromToken(principal) { user -> - if (user.nbCoins >= participation.stake) { - participations.add( - Participation( - id = UUID.randomUUID().toString(), - betId = participation.betId, - username = user.username, - answer = participation.answer, - stake = participation.stake - ) - ) - call.respond(HttpStatusCode.Created) - } else { - call.respond(HttpStatusCode.Forbidden, ApiMessage.NotEnoughCoins) - } - } - } - } - delete("/participations/delete") { - hasToken { principal -> - val participationId = call.receive() - participations.find { it.id == participationId }?.let { participation -> - verifyUserFromToken(principal) { user -> - user.nbCoins += participation.stake - participations.remove(participation) - call.respond(HttpStatusCode.NoContent) - } - } ?: call.respond(HttpStatusCode.NotFound, ApiMessage.ParticipationNotFound) - } - } - } - } -} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/UserRouter.kt b/Sources/src/main/kotlin/allin/routing/UserRouter.kt index 5cf3ba3..18489be 100644 --- a/Sources/src/main/kotlin/allin/routing/UserRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/UserRouter.kt @@ -1,92 +1,81 @@ package allin.routing -import allin.dto.convertUserToUserDTO -import allin.dto.convertUserToUserDTOToken -import allin.ext.hasToken -import allin.ext.verifyUserFromToken -import allin.model.ApiMessage +import allin.dto.* 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.auth.jwt.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import java.util.* val users = mutableListOf() -val RegexCheckerUser = AppConfig.regexChecker -val CryptManagerUser = AppConfig.cryptManager -val tokenManagerUser = AppConfig.tokenManager -const val DEFAULT_COINS = 500 +val RegexCheckerUser= AppConfig.regexChecker +val CryptManagerUser= AppConfig.cryptManager +val tokenManagerUser=AppConfig.tokenManager + fun Application.UserRouter() { routing { - route("/users/register") { + route("/users/register"){ post { - val tempUser = call.receive() - if (RegexCheckerUser.isEmailInvalid(tempUser.email)) { - call.respond(HttpStatusCode.Forbidden, ApiMessage.InvalidMail) + val TempUser = call.receive() + if (RegexCheckerUser.isEmailInvalid(TempUser.email)){ + call.respond(HttpStatusCode.Forbidden,"Input a valid mail !") } - users.find { it.username == tempUser.username || it.email == tempUser.email }?.let { user -> - 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) - users.add(user) - call.respond(HttpStatusCode.Created, user) + val user = users.find { it.username == TempUser.username || it.email == TempUser.email } + if(user == null) { + CryptManagerUser.passwordCrypt(TempUser) + TempUser.token=tokenManagerUser.generateOrReplaceJWTToken(TempUser) + users.add(TempUser) + call.respond(HttpStatusCode.Created, TempUser) } + call.respond(HttpStatusCode.Conflict,"Mail or/and username already exist") } } route("/users/login") { post { val checkUser = call.receive() - users.find { it.username == checkUser.login || it.email == checkUser.login }?.let { user -> - if (CryptManagerUser.passwordDecrypt(user, checkUser.password)) { - user.token = tokenManagerUser.generateOrReplaceJWTToken(user) - call.respond(HttpStatusCode.OK, convertUserToUserDTOToken(user)) - } else { - call.respond(HttpStatusCode.NotFound, ApiMessage.IncorrectLoginPassword) - } - } ?: call.respond(HttpStatusCode.NotFound, ApiMessage.IncorrectLoginPassword) + val user = users.find { it.username == checkUser.login || it.email == checkUser.login } + if (user != null && CryptManagerUser.passwordDecrypt(user,checkUser.password)) { + user.token=tokenManagerUser.generateOrReplaceJWTToken(user) + call.respond(HttpStatusCode.OK, convertUserToUserDTOToken(user)) + } else { + call.respond(HttpStatusCode.NotFound,"Login and/or password incorrect.") + } } } - authenticate { - post("/users/delete") { - hasToken { principal -> - verifyUserFromToken(principal) { user -> - val checkUser = call.receive() - if (user.username == checkUser.login && user.password == checkUser.password) { - users.remove(user) - call.respond(HttpStatusCode.Accepted, convertUserToUserDTO(user)) - } else { - call.respond(HttpStatusCode.NotFound, ApiMessage.IncorrectLoginPassword) - } - } + route("/users/delete") { + post { + val checkUser = call.receive() + val user = users.find { it.username == checkUser.login || it.email == checkUser.login } + if (user != null && user.password == checkUser.password) { + users.remove(user) + call.respond(HttpStatusCode.Accepted,convertUserToUserDTO(user)) + } else { + call.respond(HttpStatusCode.NotFound,"Login and/or password incorrect.") } } - + } + + authenticate { get("/users/token") { - hasToken { principal -> - verifyUserFromToken(principal) { user -> - call.respond(HttpStatusCode.OK, convertUserToUserDTO(user)) - } + val principal = call.principal() + val username = principal!!.payload.getClaim("username").asString() + val user = users.find { it.username == username } + if (user != null) { + call.respond(HttpStatusCode.OK,convertUserToUserDTO(user)) + } else { + call.respond(HttpStatusCode.NotFound, "User not found with the valid token !") } } } + } } diff --git a/Sources/src/main/kotlin/allin/serializer/DateSerializer.kt b/Sources/src/main/kotlin/allin/serializer/DateSerializer.kt index e18b220..6002c28 100644 --- a/Sources/src/main/kotlin/allin/serializer/DateSerializer.kt +++ b/Sources/src/main/kotlin/allin/serializer/DateSerializer.kt @@ -1,25 +1,27 @@ package allin.serializer -import kotlinx.serialization.KSerializer +import kotlinx.serialization.* import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import java.time.Instant -import java.time.ZoneId -import java.time.ZonedDateTime +import java.text.SimpleDateFormat +import java.util.* -object ZonedDateTimeSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.LONG) +@Serializer(Date::class) +class DateSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ZonedDateTime) { - encoder.encodeLong(value.toEpochSecond()) + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.FRANCE) + + override fun deserialize(decoder: Decoder): Date { + val dateString = decoder.decodeString() + return formatter.parse(dateString) } - override fun deserialize(decoder: Decoder): ZonedDateTime { - val epoch = decoder.decodeLong() - return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epoch), ZoneId.systemDefault()) + override fun serialize(encoder: Encoder, value: Date) { + val dateString = formatter.format(value) + encoder.encodeString(dateString) } } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/utils/TokenManager.kt b/Sources/src/main/kotlin/allin/utils/TokenManager.kt index dbebe03..d35ede5 100644 --- a/Sources/src/main/kotlin/allin/utils/TokenManager.kt +++ b/Sources/src/main/kotlin/allin/utils/TokenManager.kt @@ -10,18 +10,19 @@ import java.util.* class TokenManager private constructor(val config: HoconApplicationConfig) { - val audience = config.property("audience").getString() - val secret = config.property("secret").getString() - val issuer = config.property("issuer").getString() - fun generateJWTToken(user: User): String { + val audience=config.property("audience").getString() + val secret=config.property("secret").getString() + val issuer=config.property("issuer").getString() + fun generateJWTToken(user : User): String { val expirationDate = System.currentTimeMillis() + 604800000 // une semaine en miliseconde - return JWT.create() + val token = JWT.create() .withAudience(audience) .withIssuer(issuer) .withClaim("username", user.username) .withExpiresAt(Date(expirationDate)) .sign(Algorithm.HMAC256(secret)) + return token } fun verifyJWTToken(): JWTVerifier { @@ -33,10 +34,10 @@ class TokenManager private constructor(val config: HoconApplicationConfig) { fun generateOrReplaceJWTToken(user: User): String { val userToken = getUserToken(user) - return if (userToken != null && !isTokenExpired(userToken)) { - userToken + if (userToken != null && !isTokenExpired(userToken)) { + return userToken } else { - generateJWTToken(user) + return generateJWTToken(user) } } @@ -49,11 +50,10 @@ class TokenManager private constructor(val config: HoconApplicationConfig) { return user.token } - fun getUsernameFromToken(token: String): String { + fun getUsernameFromToken(token: String) : String{ val decodedJWT: DecodedJWT = JWT.decode(token) return decodedJWT.getClaim("username").asString() } - companion object { private var instance: TokenManager? = null fun getInstance(config: HoconApplicationConfig): TokenManager {