Refactor and fixes
continuous-integration/drone/push Build is passing Details

pull/12/head
avalin 11 months ago
parent 0621d8c406
commit cae02af684

@ -33,6 +33,7 @@ private val allInDataSource: AllInDataSource = when (data_source) {
"postgres" -> PostgresDataSource() "postgres" -> PostgresDataSource()
else -> MockDataSource() else -> MockDataSource()
} }
val Application.dataSource: AllInDataSource val Application.dataSource: AllInDataSource
get() = allInDataSource get() = allInDataSource
@ -79,11 +80,11 @@ private fun Application.extracted() {
} }
} }
BasicRouting() basicRouter()
UserRouter() userRouter()
BetRouter() betRouter()
ParticipationRouter() participationRouter()
BetDetailRouter() betDetailRouter()
kronJob(BET_VERIFY_DELAY) { kronJob(BET_VERIFY_DELAY) {
dataSource.betDataSource.updateBetStatuses(ZonedDateTime.now()) dataSource.betDataSource.updateBetStatuses(ZonedDateTime.now())

@ -4,7 +4,6 @@ import allin.dto.UserDTO
import allin.model.User import allin.model.User
interface UserDataSource { interface UserDataSource {
fun getUserByUsername(username: String): Pair<UserDTO?, String?> fun getUserByUsername(username: String): Pair<UserDTO?, String?>
fun addUser(user: User) fun addUser(user: User)
fun deleteUser(username: String): Boolean fun deleteUser(username: String): Boolean

@ -6,11 +6,11 @@ import allin.model.BetStatus.*
import java.time.ZonedDateTime import java.time.ZonedDateTime
class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource { class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource {
private val bets = mockData.bets private val bets by lazy { mockData.bets }
private val results = mockData.results private val results by lazy { mockData.results }
private val users = mockData.users private val users by lazy { mockData.users }
private val participations = mockData.participations private val participations by lazy { mockData.participations }
private val resultNotifications = mockData.resultNotifications private val resultNotifications by lazy { mockData.resultNotifications }
override fun getAllBets(): List<Bet> = bets override fun getAllBets(): List<Bet> = bets
override fun getBetById(id: String): Bet? = override fun getBetById(id: String): Bet? =
@ -39,7 +39,7 @@ class MockBetDataSource(mockData: MockDataSource.MockData) : BetDataSource {
if (date >= bet.endBet) { if (date >= bet.endBet) {
bets[idx] = bet.copy(status = CLOSING) bets[idx] = bet.copy(status = CLOSING)
} else { } else {
bets[idx] = bet.copy(status = WAITING) bets[idx] = bet.copy(status = IN_PROGRESS)
} }
} }
} }

@ -12,6 +12,10 @@ import java.time.ZonedDateTime
class MockDataSource : AllInDataSource() { class MockDataSource : AllInDataSource() {
init {
println("APP STARTING ON MOCK DATA SOURCE")
}
class MockData { class MockData {
val bets by lazy { mutableListOf<Bet>() } val bets by lazy { mutableListOf<Bet>() }
val results by lazy { mutableListOf<BetResult>() } val results by lazy { mutableListOf<BetResult>() }
@ -23,7 +27,7 @@ class MockDataSource : AllInDataSource() {
private val mockData by lazy { MockData() } private val mockData by lazy { MockData() }
override val userDataSource: UserDataSource = MockUserDataSource(mockData) override val userDataSource: UserDataSource by lazy { MockUserDataSource(mockData) }
override val betDataSource: BetDataSource = MockBetDataSource(mockData) override val betDataSource: BetDataSource by lazy { MockBetDataSource(mockData) }
override val participationDataSource: ParticipationDataSource = MockParticipationDataSource(mockData) override val participationDataSource: ParticipationDataSource by lazy { MockParticipationDataSource(mockData) }
} }

@ -4,7 +4,7 @@ import allin.data.ParticipationDataSource
import allin.model.Participation import allin.model.Participation
class MockParticipationDataSource(mockData: MockDataSource.MockData) : ParticipationDataSource { class MockParticipationDataSource(mockData: MockDataSource.MockData) : ParticipationDataSource {
private val participations = mockData.participations private val participations by lazy { mockData.participations }
override fun addParticipation(participation: Participation) { override fun addParticipation(participation: Participation) {
participations += participation participations += participation

@ -6,9 +6,8 @@ import allin.model.User
import java.time.ZonedDateTime import java.time.ZonedDateTime
class MockUserDataSource(mockData: MockDataSource.MockData) : UserDataSource { class MockUserDataSource(mockData: MockDataSource.MockData) : UserDataSource {
private val users = mockData.users private val users by lazy { mockData.users }
private val lastGifts = mockData.lastGifts private val lastGifts by lazy { mockData.lastGifts }
override fun getUserByUsername(username: String): Pair<UserDTO?, String?> = override fun getUserByUsername(username: String): Pair<UserDTO?, String?> =
users.find { it.username == username }?.let { users.find { it.username == username }?.let {

@ -200,7 +200,7 @@ class PostgresBetDataSource(private val database: Database) : BetDataSource {
override fun updateBetStatuses(date: ZonedDateTime) { override fun updateBetStatuses(date: ZonedDateTime) {
database.update(BetsEntity) { database.update(BetsEntity) {
set(BetsEntity.status, BetStatus.WAITING) set(BetsEntity.status, BetStatus.IN_PROGRESS)
where { where {
(date.toInstant() greaterEq BetsEntity.endRegistration) and (date.toInstant() greaterEq BetsEntity.endRegistration) and
(date.toInstant() less BetsEntity.endBet) and (date.toInstant() less BetsEntity.endBet) and

@ -4,7 +4,7 @@ import allin.data.AllInDataSource
import allin.data.BetDataSource import allin.data.BetDataSource
import allin.data.ParticipationDataSource import allin.data.ParticipationDataSource
import allin.data.UserDataSource import allin.data.UserDataSource
import allin.utils.Execute import allin.ext.execute
import org.ktorm.database.Database import org.ktorm.database.Database
class PostgresDataSource : AllInDataSource() { class PostgresDataSource : AllInDataSource() {
@ -16,14 +16,17 @@ class PostgresDataSource : AllInDataSource() {
val dbUser = System.getenv()["POSTGRES_USER"] val dbUser = System.getenv()["POSTGRES_USER"]
val dbPassword = System.getenv()["POSTGRES_PASSWORD"] val dbPassword = System.getenv()["POSTGRES_PASSWORD"]
val dbHost = System.getenv()["POSTGRES_HOST"] val dbHost = System.getenv()["POSTGRES_HOST"]
val url = "jdbc:postgresql://$dbHost/$dbDatabase"
println("APP STARTING ON POSTGRESQL DATA SOURCE $url")
database = Database.connect( database = Database.connect(
url = "jdbc:postgresql://$dbHost/$dbDatabase", url = url,
user = dbUser, user = dbUser,
password = dbPassword password = dbPassword
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF not exists utilisateur ( CREATE TABLE IF not exists utilisateur (
id VARCHAR(255) PRIMARY KEY, id VARCHAR(255) PRIMARY KEY,
@ -36,7 +39,7 @@ class PostgresDataSource : AllInDataSource() {
""".trimIndent() """.trimIndent()
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF not exists bet ( CREATE TABLE IF not exists bet (
id VARCHAR(255) PRIMARY KEY, id VARCHAR(255) PRIMARY KEY,
@ -52,7 +55,7 @@ class PostgresDataSource : AllInDataSource() {
""".trimIndent() """.trimIndent()
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF NOT EXISTS betresult ( CREATE TABLE IF NOT EXISTS betresult (
betid VARCHAR(255) PRIMARY KEY REFERENCES bet, betid VARCHAR(255) PRIMARY KEY REFERENCES bet,
@ -61,7 +64,7 @@ class PostgresDataSource : AllInDataSource() {
""".trimIndent() """.trimIndent()
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF NOT EXISTS betresultnotification ( CREATE TABLE IF NOT EXISTS betresultnotification (
betid VARCHAR(255), betid VARCHAR(255),
@ -71,7 +74,7 @@ class PostgresDataSource : AllInDataSource() {
""".trimIndent() """.trimIndent()
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF NOT EXISTS participation ( CREATE TABLE IF NOT EXISTS participation (
id VARCHAR(255) PRIMARY KEY, id VARCHAR(255) PRIMARY KEY,
@ -83,7 +86,7 @@ class PostgresDataSource : AllInDataSource() {
""".trimIndent() """.trimIndent()
) )
database.Execute( database.execute(
""" """
CREATE TABLE IF NOT EXISTS response ( CREATE TABLE IF NOT EXISTS response (
id VARCHAR(255), id VARCHAR(255),
@ -94,7 +97,7 @@ class PostgresDataSource : AllInDataSource() {
) )
} }
override val userDataSource: UserDataSource = PostgresUserDataSource(database) override val userDataSource: UserDataSource by lazy { PostgresUserDataSource(database) }
override val betDataSource: BetDataSource = PostgresBetDataSource(database) override val betDataSource: BetDataSource by lazy { PostgresBetDataSource(database) }
override val participationDataSource: ParticipationDataSource = PostgresParticipationDataSource(database) override val participationDataSource: ParticipationDataSource by lazy { PostgresParticipationDataSource(database) }
} }

@ -3,8 +3,8 @@ package allin.data.postgres
import allin.data.UserDataSource import allin.data.UserDataSource
import allin.data.postgres.entities.UsersEntity import allin.data.postgres.entities.UsersEntity
import allin.dto.UserDTO import allin.dto.UserDTO
import allin.ext.executeWithResult
import allin.model.User import allin.model.User
import allin.utils.ExecuteWithResult
import org.ktorm.database.Database import org.ktorm.database.Database
import org.ktorm.database.use import org.ktorm.database.use
import org.ktorm.dsl.* import org.ktorm.dsl.*
@ -70,7 +70,7 @@ class PostgresUserDataSource(private val database: Database) : UserDataSource {
override fun canHaveDailyGift(username: String): Boolean { override fun canHaveDailyGift(username: String): Boolean {
val request = val request =
"SELECT CASE WHEN DATE(NOW()) > DATE(lastgift) THEN true ELSE false END AS is_lastgift_greater_than_1_day FROM utilisateur WHERE username = '$username';" "SELECT CASE WHEN DATE(NOW()) > DATE(lastgift) THEN true ELSE false END AS is_lastgift_greater_than_1_day FROM utilisateur WHERE username = '$username';"
val resultSet = database.ExecuteWithResult(request) val resultSet = database.executeWithResult(request)
resultSet?.use { resultSet?.use {
if (resultSet.next()) { if (resultSet.next()) {

@ -1,18 +1,12 @@
package allin.dto package allin.dto
import io.github.smiley4.ktorswaggerui.dsl.Example
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class UserDTO( data class UserDTO(
@Example("cabb366c-5a47-4b0f-81e1-25a08fe2c2fe")
val id: String, val id: String,
@Example("Steve")
val username: String, val username: String,
@Example("stevemaroco@gmail.com")
val email: String, val email: String,
@Example("16027")
val nbCoins: Int, val nbCoins: Int,
@Example("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwLyIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwidXNlcm5hbWUiOiJ0ZXN0IiwiZXhwIjoxNzA3OTIyNjY1fQ.TwaT9Rd4Xkhg3l4fHiba0IEqnM7xUGJVFRrr5oaWOwQ")
var token: String? var token: String?
) )

@ -1,9 +1,9 @@
package allin.utils package allin.ext
import org.ktorm.database.Database import org.ktorm.database.Database
import java.sql.ResultSet import java.sql.ResultSet
fun Database.ExecuteWithResult(request: String): ResultSet? { fun Database.executeWithResult(request: String): ResultSet? {
try { try {
if (request.isNotEmpty()) { if (request.isNotEmpty()) {
return this.useTransaction { transaction -> return this.useTransaction { transaction ->
@ -19,7 +19,7 @@ fun Database.ExecuteWithResult(request: String): ResultSet? {
return null return null
} }
fun Database.Execute(request: String) { fun Database.execute(request: String) {
if (request.isNotEmpty()) if (request.isNotEmpty())
this.useTransaction { this.useTransaction {
val connection = it.connection val connection = it.connection

@ -1,6 +1,6 @@
package allin.model package allin.model
import allin.model.BetStatus.IN_PROGRESS import allin.model.BetStatus.WAITING
import allin.serializer.ZonedDateTimeSerializer import allin.serializer.ZonedDateTimeSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -10,7 +10,7 @@ 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 status: BetStatus = WAITING,
val type: BetType, 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,

@ -1,43 +1,30 @@
package allin.model package allin.model
import io.github.smiley4.ktorswaggerui.dsl.Example
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlin.random.Random
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(
@Example("cabb366c-5a47-4b0f-81e1-25a08fe2c2fe")
val id: String, val id: String,
@Example("Steve")
val username: String, val username: String,
@Example("stevemaroco@gmail.com")
val email: String, val email: String,
@Example("MarocoSteveHDOIU978*")
var password: String, var password: String,
@Example("16027") var nbCoins: Int = DEFAULT_COIN_AMOUNT,
var nbCoins: Int = 500,
@Example("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwLyIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwidXNlcm5hbWUiOiJ0ZXN0IiwiZXhwIjoxNzA3OTIyNjY1fQ.TwaT9Rd4Xkhg3l4fHiba0IEqnM7xUGJVFRrr5oaWOwQ")
var token: String? = null var token: String? = null
) )
@Serializable @Serializable
data class UserRequest( data class UserRequest(
@Example("Steve")
val username: String, val username: String,
@Example("stevemaroco@gmail.com")
val email: String, val email: String,
@Example("MarocoSteveHDOIU978*")
var password: String var password: String
) )
@Serializable @Serializable
data class CheckUser( data class CheckUser(
@Example("stevemaroco@gmail.com")
val login: String, val login: String,
@Example("MarocoSteveHDOIU978*")
val password: String val password: String
) )
fun getDailyGift() : Int{
return Random.nextInt(10,150)
}

@ -8,7 +8,7 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Application.BasicRouting() { fun Application.basicRouter() {
routing { routing {
get("/", { get("/", {
description = "Hello World of Allin API" description = "Hello World of Allin API"

@ -16,7 +16,7 @@ import io.ktor.server.routing.*
import java.util.* import java.util.*
fun Application.BetDetailRouter() { fun Application.betDetailRouter() {
val userDataSource = this.dataSource.userDataSource val userDataSource = this.dataSource.userDataSource
val betDataSource = this.dataSource.betDataSource val betDataSource = this.dataSource.betDataSource
val participationDataSource = this.dataSource.participationDataSource val participationDataSource = this.dataSource.participationDataSource

@ -7,7 +7,6 @@ import allin.model.*
import allin.utils.AppConfig import allin.utils.AppConfig
import io.github.smiley4.ktorswaggerui.dsl.get import io.github.smiley4.ktorswaggerui.dsl.get
import io.github.smiley4.ktorswaggerui.dsl.post import io.github.smiley4.ktorswaggerui.dsl.post
import io.github.smiley4.ktorswaggerui.dsl.route
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,13 +14,11 @@ import io.ktor.server.auth.jwt.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.swagger.annotations.Api
import java.util.* import java.util.*
val tokenManagerBet = AppConfig.tokenManager val tokenManagerBet = AppConfig.tokenManager
fun Application.betRouter() {
fun Application.BetRouter() {
val userDataSource = this.dataSource.userDataSource val userDataSource = this.dataSource.userDataSource
val betDataSource = this.dataSource.betDataSource val betDataSource = this.dataSource.betDataSource
val participationDataSource = this.dataSource.participationDataSource val participationDataSource = this.dataSource.participationDataSource

@ -18,7 +18,7 @@ import io.ktor.server.routing.*
import java.util.* import java.util.*
fun Application.ParticipationRouter() { fun Application.participationRouter() {
val userDataSource = this.dataSource.userDataSource val userDataSource = this.dataSource.userDataSource
val participationDataSource = this.dataSource.participationDataSource val participationDataSource = this.dataSource.participationDataSource

@ -15,7 +15,6 @@ import io.ktor.server.auth.jwt.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.swagger.annotations.Api
import java.util.* import java.util.*
val RegexCheckerUser = AppConfig.regexChecker val RegexCheckerUser = AppConfig.regexChecker
@ -24,7 +23,7 @@ val tokenManagerUser = AppConfig.tokenManager
const val DEFAULT_COINS = 500 const val DEFAULT_COINS = 500
fun Application.UserRouter() { fun Application.userRouter() {
val userDataSource = this.dataSource.userDataSource val userDataSource = this.dataSource.userDataSource
@ -39,8 +38,8 @@ fun Application.UserRouter() {
response { response {
HttpStatusCode.Created to { HttpStatusCode.Created to {
description = "User created" description = "User created"
body<User>{ body<User> {
description="The new user" description = "The new user"
} }
} }
HttpStatusCode.Conflict to { HttpStatusCode.Conflict to {
@ -189,7 +188,7 @@ fun Application.UserRouter() {
hasToken { principal -> hasToken { principal ->
verifyUserFromToken(userDataSource, principal) { userDto, _ -> verifyUserFromToken(userDataSource, principal) { userDto, _ ->
if (userDataSource.canHaveDailyGift(userDto.username)) { if (userDataSource.canHaveDailyGift(userDto.username)) {
val dailyGift = getDailyGift() val dailyGift = (DAILY_GIFT_MIN..DAILY_GIFT_MAX).random()
userDataSource.addCoins(userDto.username, dailyGift) userDataSource.addCoins(userDto.username, dailyGift)
call.respond(HttpStatusCode.OK, dailyGift) call.respond(HttpStatusCode.OK, dailyGift)
} else call.respond(HttpStatusCode.MethodNotAllowed, ApiMessage.NO_GIFT) } else call.respond(HttpStatusCode.MethodNotAllowed, ApiMessage.NO_GIFT)

@ -1,5 +1,6 @@
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
@ -7,13 +8,11 @@ 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,11 +1,11 @@
package allin.utils package allin.utils
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import io.ktor.server.config.HoconApplicationConfig import io.ktor.server.config.*
object AppConfig { object AppConfig {
val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load()) private val config: HoconApplicationConfig = HoconApplicationConfig(ConfigFactory.load())
val tokenManager = TokenManager.getInstance(config) val tokenManager = TokenManager.getInstance(config)
val regexChecker= RegexChecker() val regexChecker = RegexChecker()
val cryptManager = CryptManager() val cryptManager = CryptManager()
} }

@ -4,9 +4,12 @@ import allin.model.User
import org.mindrot.jbcrypt.BCrypt import org.mindrot.jbcrypt.BCrypt
class CryptManager { class CryptManager {
val salt=addDollarsSecrets(System.getenv().get("SALT").toString()) private val salt = addDollarsSecrets(
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
fun addDollarsSecrets(chaine: String): String { private 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, '$')
@ -14,17 +17,20 @@ class CryptManager {
return stringBuilder.toString() return stringBuilder.toString()
} }
fun passwordCrypt(password : String): String {
return BCrypt.hashpw(password,salt) fun passwordCrypt(password: String): String {
return BCrypt.hashpw(password, salt)
} }
fun passwordCrypt(user: User){
user.password=BCrypt.hashpw(user.password,salt) fun passwordCrypt(user: User) {
user.password = BCrypt.hashpw(user.password, salt)
} }
fun passwordDecrypt(password: String, passwordClear: String): Boolean{
return BCrypt.hashpw(passwordClear,salt)==password fun passwordDecrypt(password: String, passwordClear: String): Boolean {
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)
} }
} }

@ -2,9 +2,8 @@ package allin.utils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.ExperimentalTime
@OptIn(DelicateCoroutinesApi::class, ExperimentalTime::class) @OptIn(DelicateCoroutinesApi::class)
fun kronJob(duration: Duration, action: () -> Unit) = fun kronJob(duration: Duration, action: () -> Unit) =
GlobalScope.launch { GlobalScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {

Loading…
Cancel
Save