├── Procfile ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.conf │ │ │ ├── hikari.properties │ │ │ └── logback.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── moviebox │ │ │ └── backend │ │ │ ├── module │ │ │ ├── test │ │ │ │ └── TestRoutes.kt │ │ │ ├── ApplicationModule.kt │ │ │ └── user │ │ │ │ ├── UserRoutes.kt │ │ │ │ └── UserController.kt │ │ │ ├── domain │ │ │ ├── services │ │ │ │ └── user │ │ │ │ │ ├── UserService.kt │ │ │ │ │ └── UserServiceImpl.kt │ │ │ ├── DomainModule.kt │ │ │ ├── encrypt │ │ │ │ ├── BCrypt.kt │ │ │ │ └── JwtProvider.kt │ │ │ ├── extension │ │ │ │ └── Regex.kt │ │ │ ├── repository │ │ │ │ └── UserRepository.kt │ │ │ └── model │ │ │ │ ├── User.kt │ │ │ │ └── ErrorException.kt │ │ │ ├── data │ │ │ ├── DataModule.kt │ │ │ ├── database │ │ │ │ ├── DatabaseFactory.kt │ │ │ │ └── dao │ │ │ │ │ └── Users.kt │ │ │ └── repository │ │ │ │ └── UserRepositoryImpl.kt │ │ │ └── Application.kt │ └── test │ │ └── kotlin │ │ └── com.moviebox.backend │ │ └── ApplicationTest.kt └── build.gradle.kts ├── gradle.properties ├── .editorconfig ├── .gitignore ├── settings.gradle.kts ├── gradlew.bat ├── README.md ├── gradlew ├── LICENSE.md └── detekt.yml /Procfile: -------------------------------------------------------------------------------- 1 | web: ./build/install/moviebox/bin/moviebox -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorkik/MovieBox-Backend/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | port = ${?PORT} 5 | } 6 | application { 7 | modules = [ com.moviebox.backend.ApplicationKt.module ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/src/main/resources/hikari.properties: -------------------------------------------------------------------------------- 1 | dataSourceClassName=org.postgresql.ds.PGSimpleDataSource 2 | dataSource.user=postgres 3 | dataSource.password=password 4 | dataSource.databaseName=backend_db 5 | dataSource.portNumber=5432 6 | dataSource.serverName=localhost -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/module/test/TestRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.module.test 2 | 3 | import io.ktor.routing.Routing 4 | import io.ktor.routing.get 5 | import io.ktor.response.respondText 6 | 7 | fun Routing.test() { 8 | get("/") { 9 | context.respondText("Test") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/module/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.module 2 | 3 | import com.moviebox.backend.module.user.UserController 4 | import org.koin.dsl.module 5 | 6 | val applicationModule = module { 7 | /** 8 | * Controller 9 | */ 10 | 11 | single { UserController(get()) } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/services/user/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.services.user 2 | 3 | import com.moviebox.backend.domain.model.User 4 | 5 | interface UserService { 6 | fun create(user: User): User 7 | 8 | fun authenticate(user: User): User 9 | 10 | fun getByEmail(email: String): User 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/data/DataModule.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.data 2 | 3 | import com.moviebox.backend.data.repository.UserRepositoryImpl 4 | import com.moviebox.backend.domain.repository.UserRepository 5 | import org.koin.dsl.module 6 | 7 | val dataModule = module { 8 | /** 9 | * Repository 10 | */ 11 | single { UserRepositoryImpl() } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/DomainModule.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain 2 | 3 | import com.moviebox.backend.domain.encrypt.JwtProvider 4 | import com.moviebox.backend.domain.services.user.UserService 5 | import com.moviebox.backend.domain.services.user.UserServiceImpl 6 | import org.koin.dsl.module 7 | 8 | val domainModule = module { 9 | /** 10 | * Service 11 | */ 12 | single { UserServiceImpl(JwtProvider, get()) } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/encrypt/BCrypt.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.encrypt 2 | 3 | import at.favre.lib.crypto.bcrypt.BCrypt 4 | 5 | object BCrypt { 6 | fun encrypt(password: String): String = BCrypt.withDefaults().hashToString(12, password.toCharArray()) 7 | 8 | fun verify(password: String, bcryptPasswordHash: String): Boolean = BCrypt.verifyer().verify( 9 | password.toCharArray(), 10 | bcryptPasswordHash 11 | ).verified 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 3 | org.gradle.daemon=true 4 | org.gradle.parallel=true 5 | org.gradle.caching=true 6 | org.gradle.configureondemand=true 7 | #Kotlin 8 | kotlin.code.style=official 9 | kapt.use.worker.api=true 10 | # Enable Compile Avoidance, which skips annotation processing if only method bodies are changed in dependencies 11 | # To turn on Compile Avoidance we need to turn off AP discovery in compile path. 12 | kapt.include.compile.classpath=true -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/module/user/UserRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.module.user 2 | 3 | import io.ktor.routing.Routing 4 | import io.ktor.routing.route 5 | import io.ktor.routing.post 6 | import org.koin.ktor.ext.inject 7 | 8 | fun Routing.users() { 9 | val controller: UserController by inject() 10 | 11 | route("/users") { 12 | post { 13 | controller.register(context) 14 | } 15 | post("login") { 16 | controller.login(context) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{kt, kts}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | max_line_length = 120 7 | 8 | # Starting from ktlint 0.34.2 There is a problem with import "import-ordering" rule - ktlint contradicts 9 | # default AS import arrangement rules, so we have to disable ktlint it. 10 | # See https://github.com/pinterest/ktlint/issues/527 11 | # Since IDE complains about unknown `disabled_rules` key we have to disable it as well: 12 | 13 | # noinspection EditorConfigKeyCorrectness 14 | disabled_rules = import-ordering,no-wildcard-imports 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # IDEs and editors 25 | /.idea 26 | .project 27 | .classpath 28 | *.launch 29 | .settings/ 30 | out/ 31 | build/ 32 | 33 | #Gradle 34 | .gradle/ 35 | 36 | #System Files 37 | .DS_Store 38 | Thumbs.db -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/extension/Regex.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.extension 2 | 3 | const val MAIL_REGEX = ( 4 | "^(([\\w-]+\\.)+[\\w-]+|([a-zA-Z]|[\\w-]{2,}))@" + 5 | "((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" + 6 | "[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\." + 7 | "([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" + 8 | "[0-9]{1,2}|25[0-5]|2[0-4][0-9]))|" + 9 | "([a-zA-Z]+[\\w-]+\\.)+[a-zA-Z]{2,4})$" 10 | ) 11 | 12 | fun String?.isEmailValid(): Boolean = !this.isNullOrBlank() && Regex(MAIL_REGEX).matches(this) 13 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | resolutionStrategy { 9 | eachPlugin { 10 | when (requested.id.id) { 11 | "koin" -> useModule("org.koin:koin-gradle-plugin:${requested.version}") 12 | "com.diffplug.spotless" -> 13 | useModule("com.diffplug.spotless:spotless-plugin-gradle:${requested.version}") 14 | } 15 | } 16 | } 17 | } 18 | 19 | rootProject.name = "moviebox" 20 | 21 | include(":app") 22 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | application { 2 | mainClass.set("io.ktor.server.netty.EngineMain") 3 | } 4 | 5 | plugins { 6 | application 7 | kotlin("jvm") 8 | } 9 | 10 | dependencies { 11 | implementation(Lib.Kotlin.kotlinStdLib) 12 | implementation(Lib.Log.logback) 13 | implementation(Lib.Ktor.core) 14 | implementation(Lib.Ktor.netty) 15 | implementation(Lib.Ktor.gson) 16 | implementation(Lib.Ktor.authJwt) 17 | 18 | implementation(Lib.Exposed.core) 19 | implementation(Lib.Exposed.dao) 20 | implementation(Lib.Exposed.jdbc) 21 | 22 | implementation(Lib.Database.hikariCp) 23 | implementation(Lib.Database.flywayCore) 24 | implementation(Lib.Database.ktorFlyway) 25 | implementation(Lib.Database.postgresql) 26 | 27 | implementation(Lib.Koin.koinLib) 28 | 29 | implementation(Lib.Other.bcrypt) 30 | 31 | testImplementation(Lib.Ktor.tests) 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/data/database/DatabaseFactory.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.data.database 2 | 3 | import com.zaxxer.hikari.HikariConfig 4 | import com.zaxxer.hikari.HikariDataSource 5 | import javax.sql.DataSource 6 | import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction 7 | 8 | object DatabaseFactory { 9 | 10 | /** 11 | * Создание инстанса DataSource 12 | */ 13 | fun create(): DataSource = hikari() 14 | 15 | /** 16 | * Все настройки находятся в файле resources/hikari.properties 17 | */ 18 | private fun hikari(): HikariDataSource { 19 | val config = HikariConfig("/hikari.properties").apply { 20 | validate() 21 | } 22 | return HikariDataSource(config) 23 | } 24 | 25 | suspend fun dbQuery( 26 | block: suspend () -> T 27 | ): T = newSuspendedTransaction { block() } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.repository 2 | 3 | import com.moviebox.backend.domain.model.User 4 | 5 | interface UserRepository { 6 | 7 | /** 8 | * Метод для создания пользователя 9 | * 10 | * @param user модель [User] пользователя с полями указанными при регистроации 11 | * 12 | * @return [User.id] уникальный идентификатор пользователя или null, если произошла ошибка или пользователя уже 13 | * создан 14 | */ 15 | fun create(user: User): Long? 16 | 17 | /** 18 | * Метод для поиска пользователя по email 19 | * 20 | * @param email почта пользователя 21 | * 22 | * @return модель пользователя [User] или null, если пользователь не найден 23 | */ 24 | 25 | fun getUserByEmail(email: String): User? 26 | 27 | fun getUserByUsername(username: String): User? 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/data/database/dao/Users.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.data.database.dao 2 | 3 | import com.moviebox.backend.domain.model.User 4 | import org.jetbrains.exposed.dao.id.LongIdTable 5 | import org.jetbrains.exposed.sql.ResultRow 6 | 7 | object Users : LongIdTable("users") { 8 | val username = varchar(name = "username", length = 100).uniqueIndex() 9 | val email = varchar(name = "email", length = 200).uniqueIndex() 10 | val password = varchar(name = "password", length = 100) 11 | val avatarUrl = varchar(name = "avatar_url", length = 500).nullable() 12 | val quot = varchar(name = "quot", length = 500).nullable() 13 | 14 | fun ResultRow.toDomain() = User( 15 | id = this[id].value, 16 | username = this[username], 17 | email = this[email], 18 | password = this[password], 19 | avatarUrl = this[avatarUrl], 20 | quot = this[quot] 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com.moviebox.backend/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend 2 | 3 | import io.ktor.http.HttpMethod 4 | import io.ktor.http.HttpStatusCode 5 | import io.ktor.server.testing.withTestApplication 6 | import io.ktor.server.testing.handleRequest 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Test 9 | 10 | class ApplicationTest { 11 | @Test 12 | fun testRoot() { 13 | withTestApplication({ module(testing = true) }) { 14 | handleRequest(HttpMethod.Get, "/") {}.apply { 15 | assertEquals(HttpStatusCode.OK, response.status()) 16 | assertEquals("Test", response.content) 17 | } 18 | } 19 | } 20 | 21 | @Test 22 | fun testRegister() { 23 | withTestApplication({ module(testing = true) }) { 24 | handleRequest(HttpMethod.Post, "/users") {}.apply { 25 | assertEquals(HttpStatusCode.Unauthorized, response.status()) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.model 2 | 3 | import com.moviebox.backend.domain.extension.isEmailValid 4 | import io.ktor.auth.Principal 5 | 6 | data class User( 7 | val id: Long? = null, 8 | val username: String, 9 | val email: String, 10 | val password: String? = null, 11 | val avatarUrl: String? = null, 12 | val quot: String? = null, 13 | val token: String? = null 14 | ) : Principal 15 | 16 | fun User?.isValidLogin(): User { 17 | if (this != null && username.isNotBlank() && !password.isNullOrBlank()) { 18 | return this 19 | } 20 | 21 | throw ErrorException.InvalidAuthorizationData 22 | } 23 | 24 | @Suppress("ComplexCondition") 25 | fun User?.isValidRegister(): User { 26 | if (this != null && username.isNotBlank() && !password.isNullOrBlank() && email.isEmailValid()) { 27 | return this 28 | } 29 | throw ErrorException.InvalidAuthorizationData 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/module/user/UserController.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.module.user 2 | 3 | import com.moviebox.backend.domain.model.User 4 | import com.moviebox.backend.domain.model.isValidLogin 5 | import com.moviebox.backend.domain.model.isValidRegister 6 | import com.moviebox.backend.domain.services.user.UserService 7 | import io.ktor.application.ApplicationCall 8 | import io.ktor.request.receiveOrNull 9 | import io.ktor.response.respond 10 | 11 | class UserController(private val service: UserService) { 12 | 13 | suspend fun register(call: ApplicationCall) { 14 | val userRequest = call.receiveOrNull() 15 | 16 | service.create(userRequest.isValidRegister()).apply { 17 | call.respond(this) 18 | } 19 | } 20 | 21 | suspend fun login(call: ApplicationCall) { 22 | val userRequest = call.receiveOrNull() 23 | 24 | service.authenticate(userRequest.isValidLogin()).apply { 25 | call.respond(this) 26 | } 27 | } 28 | 29 | fun getUserByEmail(email: String?): User { 30 | return email.let { 31 | require(!it.isNullOrBlank()) { "User not logged or with invalid email." } 32 | service.getByEmail(it) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/encrypt/JwtProvider.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.encrypt 2 | 3 | import com.auth0.jwt.JWT 4 | import com.auth0.jwt.JWTVerifier 5 | import com.auth0.jwt.algorithms.Algorithm 6 | import com.auth0.jwt.interfaces.DecodedJWT 7 | import java.util.Date 8 | 9 | object JwtProvider { 10 | private val algorithm = Algorithm.HMAC256("secret-key") 11 | 12 | private const val validityInMs = 36_000_00 * 168 // 1 week 13 | const val issuer = "ktor-realworld" 14 | const val audience = "ktor-audience" 15 | 16 | private const val subject = "Authentication" 17 | 18 | private const val claim_email = "email" 19 | private const val claim_username = "username" 20 | 21 | val verifier: JWTVerifier = JWT 22 | .require(algorithm) 23 | .withIssuer(issuer) 24 | .build() 25 | 26 | fun decodeJWT(token: String): DecodedJWT = JWT.require(algorithm).build().verify(token) 27 | 28 | fun createJWT(username: String, email: String): String = JWT.create() 29 | .withIssuedAt(Date()) 30 | .withSubject(subject) 31 | .withIssuer(issuer) 32 | .withAudience(audience) 33 | .withClaim(claim_email, email) 34 | .withClaim(claim_username, username) 35 | .withExpiresAt(Date(System.currentTimeMillis() + validityInMs)).sign(algorithm) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/data/repository/UserRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.data.repository 2 | 3 | import com.moviebox.backend.data.database.dao.Users 4 | import com.moviebox.backend.data.database.dao.Users.toDomain 5 | import com.moviebox.backend.domain.model.User 6 | import com.moviebox.backend.domain.repository.UserRepository 7 | import org.jetbrains.exposed.sql.SchemaUtils 8 | import org.jetbrains.exposed.sql.insertIgnoreAndGetId 9 | import org.jetbrains.exposed.sql.select 10 | import org.jetbrains.exposed.sql.transactions.transaction 11 | 12 | class UserRepositoryImpl : UserRepository { 13 | init { 14 | transaction { 15 | SchemaUtils.create(Users) 16 | } 17 | } 18 | 19 | override fun create(user: User): Long? { 20 | return transaction { 21 | Users.insertIgnoreAndGetId { row -> 22 | row[email] = user.email 23 | row[username] = user.username 24 | row[password] = user.password!! 25 | }?.value 26 | } 27 | } 28 | 29 | override fun getUserByEmail(email: String): User? { 30 | return transaction { 31 | Users.select { Users.email eq email } 32 | .map { it.toDomain() } 33 | .firstOrNull() 34 | } 35 | } 36 | 37 | override fun getUserByUsername(username: String): User? { 38 | return transaction { 39 | Users.select { Users.username eq username } 40 | .map { it.toDomain() } 41 | .firstOrNull() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/model/ErrorException.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | sealed class ErrorException : Exception() { 6 | object InvalidAuthorizationData : ErrorException() 7 | object UsernameAlreadyExists : ErrorException() 8 | object EmailAlreadyExists : ErrorException() 9 | object InvalidPassword : ErrorException() 10 | object UserIsNotFound : ErrorException() 11 | } 12 | 13 | data class ErrorMessage( 14 | @SerializedName("status_code") val statusCode: Int, 15 | val notLocalizedMessage: String 16 | ) { 17 | override fun toString(): String = "$statusCode $notLocalizedMessage" 18 | 19 | override fun equals(other: Any?): Boolean = other is ErrorMessage && other.statusCode == statusCode 20 | 21 | override fun hashCode(): Int = statusCode.hashCode() 22 | 23 | companion object { 24 | val InvalidAuthorizationData = ErrorMessage( 25 | statusCode = 2, 26 | notLocalizedMessage = "Invalid username and/or password: You did not provide a valid login." 27 | ) 28 | 29 | val UsernameAlreadyExists = ErrorMessage( 30 | statusCode = 3, 31 | notLocalizedMessage = "User with this login already exists." 32 | ) 33 | 34 | val EmailAlreadyExists = ErrorMessage( 35 | statusCode = 4, 36 | notLocalizedMessage = "A user with the same email address already exists." 37 | ) 38 | 39 | val InvalidPassword = ErrorMessage( 40 | statusCode = 5, 41 | notLocalizedMessage = "Invalid password." 42 | ) 43 | 44 | val UserIsNotFound = ErrorMessage( 45 | statusCode = 6, 46 | notLocalizedMessage = "User is not found." 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/domain/services/user/UserServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend.domain.services.user 2 | 3 | import com.moviebox.backend.domain.encrypt.BCrypt 4 | import com.moviebox.backend.domain.encrypt.JwtProvider 5 | import com.moviebox.backend.domain.model.ErrorException 6 | import com.moviebox.backend.domain.model.User 7 | import com.moviebox.backend.domain.repository.UserRepository 8 | import io.ktor.features.NotFoundException 9 | import java.util.Base64 10 | 11 | class UserServiceImpl(private val jwtProvider: JwtProvider, private val repository: UserRepository) : UserService { 12 | private val base64Encoder = Base64.getEncoder() 13 | 14 | override fun create(user: User): User { 15 | val userInDb = repository.getUserByEmail(user.email) 16 | 17 | if (userInDb != null) throw ErrorException.EmailAlreadyExists 18 | 19 | repository.create(user.copy(password = BCrypt.encrypt(user.password!!))) 20 | return user.copy(token = generateJwtToken(user), password = null) 21 | } 22 | 23 | override fun authenticate(user: User): User { 24 | val userFound = repository.getUserByUsername(user.username) 25 | 26 | return when { 27 | userFound == null -> throw ErrorException.UserIsNotFound 28 | userFound.password == null -> throw ErrorException.UserIsNotFound 29 | BCrypt.verify(user.password!!, userFound.password) -> throw ErrorException.InvalidPassword 30 | else -> { 31 | userFound.copy(token = generateJwtToken(userFound), password = null) 32 | } 33 | } 34 | } 35 | 36 | override fun getByEmail(email: String): User { 37 | val user = repository.getUserByEmail(email) 38 | user ?: throw NotFoundException("User not found to get.") 39 | return user.copy(token = generateJwtToken(user)) 40 | } 41 | 42 | private fun generateJwtToken(user: User): String = 43 | jwtProvider.createJWT(username = user.username, email = user.email) 44 | } 45 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MovieBox Backend - Ktor 2 | 3 | [![Kotlin Version][badge-kotlin]](https://kotlinlang.org) 4 | [![Ktor version][badge-ktor]](https://ktor.io) 5 | [![Gradle][badge-gradle]](https://gradle.org) 6 | [![CodeFactor][badge-codefactor]](https://www.codefactor.io/repository/github/majorkik/moviebox-backend) 7 | 8 | #### Status 🚧 [Work in progress] 🚧 9 | 10 | ## :rocket: Project characteristics and tech-stack 11 | 12 | - [**100% Kotlin**](https://kotlinlang.org/) + [**Coroutines**](https://kotlinlang.org/docs/reference/coroutines-overview.html) - perform background operations 13 | - [**Ktor**](https://github.com/ktorio/ktor) - Framework for quickly creating connected applications in Kotlin with minimal 14 | effort 15 | - [**Logback**](https://ktor.io/docs/logging.html) - Logback is intended as a successor to the popular log4j project 16 | - [**Exposed**](https://github.com/JetBrains/Exposed) - Kotlin SQL Framework 17 | - [**Hikari**](https://github.com/brettwooldridge/HikariCP) - Solid, high-performance, JDBC connection pool at last 18 | - [**Flyway**](https://github.com/flyway/flyway) - Database Migrations Made Easy 19 | - [**PostgreSQL**](https://www.postgresql.org/) 20 | 21 | ## Getting Started 22 | 23 | ```shell 24 | $ gradle run # Application start and server start 25 | ``` 26 | 27 | ## License 28 | 29 | ``` 30 | MIT License 31 | 32 | Copyright (c) 2021 Rodion Belovitskiy 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 35 | associated documentation files (the "Software"), to deal in the Software without restriction, including 36 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to 38 | the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all copies or substantial 41 | portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 44 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 45 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 46 | WHETHER IN AN ACTION OF TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | ``` 49 | 50 | [badge-kotlin]: https://img.shields.io/badge/Kotlin-1.5.21-brightgreen?style=flat-square&logo=appveyor "Kotlin Version" 51 | 52 | [badge-ktor]: https://img.shields.io/badge/Ktor-1.6.2-red?style=flat-square&logo=appveyor "Ktor" 53 | 54 | [badge-gradle]: https://img.shields.io/badge/Gradle-7.1.1-blue?style=flat-square&logo=appveyor "Gradle" 55 | 56 | [badge-codefactor]: https://www.codefactor.io/repository/github/majorkik/moviebox-backend/badge?style=flat-square&logo=appveyor "CodeFactor" -------------------------------------------------------------------------------- /app/src/main/kotlin/com/moviebox/backend/Application.kt: -------------------------------------------------------------------------------- 1 | package com.moviebox.backend 2 | 3 | import com.moviebox.backend.data.database.DatabaseFactory 4 | import com.moviebox.backend.domain.encrypt.JwtProvider 5 | import com.moviebox.backend.module.test.test 6 | import com.moviebox.backend.data.dataModule 7 | import com.moviebox.backend.domain.domainModule 8 | import com.moviebox.backend.module.applicationModule 9 | import com.moviebox.backend.domain.model.ErrorException 10 | import com.moviebox.backend.domain.model.ErrorMessage.Companion.EmailAlreadyExists 11 | import com.moviebox.backend.domain.model.ErrorMessage.Companion.InvalidAuthorizationData 12 | import com.moviebox.backend.domain.model.ErrorMessage.Companion.InvalidPassword 13 | import com.moviebox.backend.domain.model.ErrorMessage.Companion.UserIsNotFound 14 | import com.moviebox.backend.module.user.UserController 15 | import com.moviebox.backend.module.user.users 16 | import com.viartemev.ktor.flyway.FlywayFeature 17 | import io.ktor.application.Application 18 | import io.ktor.application.install 19 | import io.ktor.application.call 20 | import io.ktor.auth.Authentication 21 | import io.ktor.auth.jwt.jwt 22 | import io.ktor.features.ContentNegotiation 23 | import io.ktor.features.CallLogging 24 | import io.ktor.features.CORS 25 | import io.ktor.features.StatusPages 26 | import io.ktor.gson.gson 27 | import io.ktor.http.HttpStatusCode 28 | import io.ktor.http.HttpMethod 29 | import io.ktor.request.path 30 | import io.ktor.response.respond 31 | import io.ktor.routing.routing 32 | import io.ktor.server.netty.EngineMain 33 | import io.ktor.util.KtorExperimentalAPI 34 | import org.jetbrains.exposed.sql.Database 35 | import org.koin.ktor.ext.Koin 36 | import org.koin.ktor.ext.inject 37 | import org.slf4j.event.Level 38 | 39 | fun main(args: Array) = EngineMain.main(args) 40 | 41 | @KtorExperimentalAPI 42 | @Suppress("unused") 43 | @JvmOverloads 44 | fun Application.module(testing: Boolean = false) { 45 | setupKoin() 46 | setupDatabase() 47 | setupNegotiation() 48 | setupLogging() 49 | setupCors() 50 | setupExceptions() 51 | 52 | setupAuthentication() 53 | 54 | routing { 55 | test() 56 | users() 57 | } 58 | } 59 | 60 | /** 61 | * Метод для инициализации сериализации 62 | */ 63 | private fun Application.setupNegotiation() { 64 | install(ContentNegotiation) { 65 | gson { 66 | setPrettyPrinting() 67 | disableHtmlEscaping() 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Метод для инициализации логирования 74 | */ 75 | private fun Application.setupLogging() { 76 | install(CallLogging) { 77 | level = Level.INFO 78 | filter { call -> call.request.path().startsWith("/") } 79 | } 80 | } 81 | 82 | /** 83 | * Метод для инициализации Koin 84 | */ 85 | 86 | private fun Application.setupKoin() { 87 | install(Koin) { 88 | modules(applicationModule, dataModule, domainModule) 89 | } 90 | } 91 | 92 | /** 93 | * Метод для инициализации CORS 94 | */ 95 | private fun Application.setupCors() { 96 | install(CORS) { 97 | method(HttpMethod.Options) 98 | method(HttpMethod.Put) 99 | method(HttpMethod.Delete) 100 | method(HttpMethod.Patch) 101 | } 102 | } 103 | 104 | /** 105 | * Метод для инициализации и настройки базы данных 106 | */ 107 | @KtorExperimentalAPI 108 | private fun Application.setupDatabase() { 109 | val database = DatabaseFactory.create() 110 | 111 | Database.connect(database) 112 | 113 | install(FlywayFeature) { 114 | dataSource = database 115 | } 116 | } 117 | 118 | /** 119 | * Метод для настройки аутентификации 120 | */ 121 | private fun Application.setupAuthentication() { 122 | val controller: UserController by inject() 123 | 124 | install(Authentication) { 125 | jwt { 126 | verifier(JwtProvider.verifier) 127 | authSchemes("Token") 128 | validate { credential -> 129 | if (credential.payload.audience.contains(JwtProvider.audience)) { 130 | controller.getUserByEmail(credential.payload.claims["email"]?.asString()) 131 | } else null 132 | } 133 | } 134 | } 135 | } 136 | 137 | private fun Application.setupExceptions() { 138 | install(StatusPages) { 139 | exception { 140 | call.respond(HttpStatusCode.Unauthorized, InvalidAuthorizationData) 141 | } 142 | 143 | exception { 144 | call.respond(HttpStatusCode.Unauthorized, EmailAlreadyExists) 145 | } 146 | 147 | exception { 148 | call.respond(HttpStatusCode.Unauthorized, InvalidPassword) 149 | } 150 | 151 | exception { 152 | call.respond(HttpStatusCode.NotFound, UserIsNotFound) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2000-2021 JetBrains s.r.o. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 0 3 | excludeCorrectable: false 4 | weights: 5 | # complexity: 2 6 | # LongParameterList: 1 7 | # style: 1 8 | # comments: 1 9 | 10 | config: 11 | validation: true 12 | warningsAsErrors: false 13 | # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' 14 | excludes: '' 15 | 16 | processors: 17 | active: true 18 | exclude: 19 | - 'DetektProgressListener' 20 | # - 'KtFileCountProcessor' 21 | # - 'PackageCountProcessor' 22 | # - 'ClassCountProcessor' 23 | # - 'FunctionCountProcessor' 24 | # - 'PropertyCountProcessor' 25 | # - 'ProjectComplexityProcessor' 26 | # - 'ProjectCognitiveComplexityProcessor' 27 | # - 'ProjectLLOCProcessor' 28 | # - 'ProjectCLOCProcessor' 29 | # - 'ProjectLOCProcessor' 30 | # - 'ProjectSLOCProcessor' 31 | # - 'LicenseHeaderLoaderExtension' 32 | 33 | console-reports: 34 | active: true 35 | exclude: 36 | - 'ProjectStatisticsReport' 37 | - 'ComplexityReport' 38 | - 'NotificationReport' 39 | # - 'FindingsReport' 40 | - 'FileBasedFindingsReport' 41 | 42 | output-reports: 43 | active: true 44 | exclude: 45 | # - 'TxtOutputReport' 46 | # - 'XmlOutputReport' 47 | # - 'HtmlOutputReport' 48 | 49 | comments: 50 | active: true 51 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 52 | AbsentOrWrongFileLicense: 53 | active: false 54 | licenseTemplateFile: 'license.template' 55 | licenseTemplateIsRegex: false 56 | CommentOverPrivateFunction: 57 | active: false 58 | CommentOverPrivateProperty: 59 | active: false 60 | EndOfSentenceFormat: 61 | active: false 62 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' 63 | UndocumentedPublicClass: 64 | active: false 65 | searchInNestedClass: true 66 | searchInInnerClass: true 67 | searchInInnerObject: true 68 | searchInInnerInterface: true 69 | UndocumentedPublicFunction: 70 | active: false 71 | UndocumentedPublicProperty: 72 | active: false 73 | 74 | complexity: 75 | active: true 76 | ComplexCondition: 77 | active: true 78 | threshold: 4 79 | ComplexInterface: 80 | active: false 81 | threshold: 10 82 | includeStaticDeclarations: false 83 | includePrivateDeclarations: false 84 | ComplexMethod: 85 | active: true 86 | threshold: 15 87 | ignoreSingleWhenExpression: false 88 | ignoreSimpleWhenEntries: false 89 | ignoreNestingFunctions: false 90 | nestingFunctions: [run, let, apply, with, also, use, forEach, isNotNull, ifNull] 91 | LabeledExpression: 92 | active: false 93 | ignoredLabels: [] 94 | LargeClass: 95 | active: true 96 | threshold: 600 97 | LongMethod: 98 | active: true 99 | threshold: 60 100 | LongParameterList: 101 | active: true 102 | functionThreshold: 6 103 | constructorThreshold: 7 104 | ignoreDefaultParameters: false 105 | ignoreDataClasses: true 106 | ignoreAnnotated: [] 107 | MethodOverloading: 108 | active: false 109 | threshold: 6 110 | NamedArguments: 111 | active: false 112 | threshold: 3 113 | NestedBlockDepth: 114 | active: true 115 | threshold: 4 116 | ReplaceSafeCallChainWithRun: 117 | active: false 118 | StringLiteralDuplication: 119 | active: false 120 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 121 | threshold: 3 122 | ignoreAnnotation: true 123 | excludeStringsWithLessThan5Characters: true 124 | ignoreStringsRegex: '$^' 125 | TooManyFunctions: 126 | active: true 127 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 128 | thresholdInFiles: 11 129 | thresholdInClasses: 11 130 | thresholdInInterfaces: 11 131 | thresholdInObjects: 11 132 | thresholdInEnums: 11 133 | ignoreDeprecated: false 134 | ignorePrivate: false 135 | ignoreOverridden: false 136 | 137 | coroutines: 138 | active: true 139 | GlobalCoroutineUsage: 140 | active: false 141 | RedundantSuspendModifier: 142 | active: false 143 | SleepInsteadOfDelay: 144 | active: false 145 | SuspendFunWithFlowReturnType: 146 | active: false 147 | 148 | empty-blocks: 149 | active: true 150 | EmptyCatchBlock: 151 | active: true 152 | allowedExceptionNameRegex: '_|(ignore|expected).*' 153 | EmptyClassBlock: 154 | active: true 155 | EmptyDefaultConstructor: 156 | active: true 157 | EmptyDoWhileBlock: 158 | active: true 159 | EmptyElseBlock: 160 | active: true 161 | EmptyFinallyBlock: 162 | active: true 163 | EmptyForBlock: 164 | active: true 165 | EmptyFunctionBlock: 166 | active: true 167 | ignoreOverridden: false 168 | EmptyIfBlock: 169 | active: true 170 | EmptyInitBlock: 171 | active: true 172 | EmptyKtFile: 173 | active: true 174 | EmptySecondaryConstructor: 175 | active: true 176 | EmptyTryBlock: 177 | active: true 178 | EmptyWhenBlock: 179 | active: true 180 | EmptyWhileBlock: 181 | active: true 182 | 183 | exceptions: 184 | active: true 185 | ExceptionRaisedInUnexpectedLocation: 186 | active: true 187 | methodNames: [toString, hashCode, equals, finalize] 188 | InstanceOfCheckForException: 189 | active: false 190 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 191 | NotImplementedDeclaration: 192 | active: false 193 | ObjectExtendsThrowable: 194 | active: false 195 | PrintStackTrace: 196 | active: true 197 | RethrowCaughtException: 198 | active: true 199 | ReturnFromFinally: 200 | active: true 201 | ignoreLabeled: false 202 | SwallowedException: 203 | active: true 204 | ignoredExceptionTypes: 205 | - InterruptedException 206 | - NumberFormatException 207 | - ParseException 208 | - MalformedURLException 209 | allowedExceptionNameRegex: '_|(ignore|expected).*' 210 | ThrowingExceptionFromFinally: 211 | active: true 212 | ThrowingExceptionInMain: 213 | active: false 214 | ThrowingExceptionsWithoutMessageOrCause: 215 | active: true 216 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 217 | exceptions: 218 | - IllegalArgumentException 219 | - IllegalStateException 220 | - IOException 221 | ThrowingNewInstanceOfSameException: 222 | active: true 223 | TooGenericExceptionCaught: 224 | active: true 225 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 226 | exceptionNames: 227 | - ArrayIndexOutOfBoundsException 228 | - Error 229 | - Exception 230 | - IllegalMonitorStateException 231 | - NullPointerException 232 | - IndexOutOfBoundsException 233 | - RuntimeException 234 | - Throwable 235 | allowedExceptionNameRegex: '_|(ignore|expected).*' 236 | TooGenericExceptionThrown: 237 | active: true 238 | exceptionNames: 239 | - Error 240 | - Exception 241 | - Throwable 242 | - RuntimeException 243 | 244 | formatting: 245 | active: true 246 | android: false 247 | autoCorrect: true 248 | AnnotationOnSeparateLine: 249 | active: false 250 | autoCorrect: true 251 | AnnotationSpacing: 252 | active: false 253 | autoCorrect: true 254 | ArgumentListWrapping: 255 | active: false 256 | autoCorrect: true 257 | ChainWrapping: 258 | active: true 259 | autoCorrect: true 260 | CommentSpacing: 261 | active: true 262 | autoCorrect: true 263 | EnumEntryNameCase: 264 | active: false 265 | autoCorrect: true 266 | Filename: 267 | active: true 268 | FinalNewline: 269 | active: true 270 | autoCorrect: true 271 | insertFinalNewLine: true 272 | ImportOrdering: 273 | active: false 274 | autoCorrect: true 275 | layout: 'idea' 276 | Indentation: 277 | active: false 278 | autoCorrect: true 279 | indentSize: 4 280 | continuationIndentSize: 4 281 | MaximumLineLength: 282 | active: true 283 | maxLineLength: 120 284 | ModifierOrdering: 285 | active: true 286 | autoCorrect: true 287 | MultiLineIfElse: 288 | active: true 289 | autoCorrect: true 290 | NoBlankLineBeforeRbrace: 291 | active: true 292 | autoCorrect: true 293 | NoConsecutiveBlankLines: 294 | active: true 295 | autoCorrect: true 296 | NoEmptyClassBody: 297 | active: true 298 | autoCorrect: true 299 | NoEmptyFirstLineInMethodBlock: 300 | active: false 301 | autoCorrect: true 302 | NoLineBreakAfterElse: 303 | active: true 304 | autoCorrect: true 305 | NoLineBreakBeforeAssignment: 306 | active: true 307 | autoCorrect: true 308 | NoMultipleSpaces: 309 | active: true 310 | autoCorrect: true 311 | NoSemicolons: 312 | active: true 313 | autoCorrect: true 314 | NoTrailingSpaces: 315 | active: true 316 | autoCorrect: true 317 | NoUnitReturn: 318 | active: true 319 | autoCorrect: true 320 | NoUnusedImports: 321 | active: true 322 | autoCorrect: true 323 | NoWildcardImports: 324 | active: true 325 | PackageName: 326 | active: true 327 | autoCorrect: true 328 | ParameterListWrapping: 329 | active: true 330 | autoCorrect: true 331 | indentSize: 4 332 | SpacingAroundAngleBrackets: 333 | active: false 334 | autoCorrect: true 335 | SpacingAroundColon: 336 | active: true 337 | autoCorrect: true 338 | SpacingAroundComma: 339 | active: true 340 | autoCorrect: true 341 | SpacingAroundCurly: 342 | active: true 343 | autoCorrect: true 344 | SpacingAroundDot: 345 | active: true 346 | autoCorrect: true 347 | SpacingAroundDoubleColon: 348 | active: false 349 | autoCorrect: true 350 | SpacingAroundKeyword: 351 | active: true 352 | autoCorrect: true 353 | SpacingAroundOperators: 354 | active: true 355 | autoCorrect: true 356 | SpacingAroundParens: 357 | active: true 358 | autoCorrect: true 359 | SpacingAroundRangeOperator: 360 | active: true 361 | autoCorrect: true 362 | SpacingAroundUnaryOperator: 363 | active: false 364 | autoCorrect: true 365 | SpacingBetweenDeclarationsWithAnnotations: 366 | active: false 367 | autoCorrect: true 368 | SpacingBetweenDeclarationsWithComments: 369 | active: false 370 | autoCorrect: true 371 | StringTemplate: 372 | active: true 373 | autoCorrect: true 374 | 375 | naming: 376 | active: true 377 | ClassNaming: 378 | active: true 379 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 380 | classPattern: '[A-Z][a-zA-Z0-9]*' 381 | ConstructorParameterNaming: 382 | active: true 383 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 384 | parameterPattern: '[a-z][A-Za-z0-9]*' 385 | privateParameterPattern: '[a-z][A-Za-z0-9]*' 386 | excludeClassPattern: '$^' 387 | ignoreOverridden: true 388 | EnumNaming: 389 | active: true 390 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 391 | enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' 392 | ForbiddenClassName: 393 | active: false 394 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 395 | forbiddenName: [] 396 | FunctionMaxLength: 397 | active: false 398 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 399 | maximumFunctionNameLength: 30 400 | FunctionMinLength: 401 | active: false 402 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 403 | minimumFunctionNameLength: 3 404 | FunctionNaming: 405 | active: true 406 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 407 | functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)' 408 | excludeClassPattern: '$^' 409 | ignoreOverridden: true 410 | ignoreAnnotated: ['Composable'] 411 | FunctionParameterNaming: 412 | active: true 413 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 414 | parameterPattern: '[a-z][A-Za-z0-9]*' 415 | excludeClassPattern: '$^' 416 | ignoreOverridden: true 417 | InvalidPackageDeclaration: 418 | active: false 419 | excludes: ['*.kts'] 420 | rootPackage: '' 421 | MatchingDeclarationName: 422 | active: true 423 | mustBeFirst: true 424 | MemberNameEqualsClassName: 425 | active: true 426 | ignoreOverridden: true 427 | NoNameShadowing: 428 | active: false 429 | NonBooleanPropertyPrefixedWithIs: 430 | active: false 431 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 432 | ObjectPropertyNaming: 433 | active: true 434 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 435 | constantPattern: '[A-Za-z][_A-Za-z0-9]*' 436 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*' 437 | privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' 438 | PackageNaming: 439 | active: true 440 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 441 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' 442 | TopLevelPropertyNaming: 443 | active: true 444 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 445 | constantPattern: '[A-Z][_A-Z0-9]*' 446 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*' 447 | privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' 448 | VariableMaxLength: 449 | active: false 450 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 451 | maximumVariableNameLength: 64 452 | VariableMinLength: 453 | active: false 454 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 455 | minimumVariableNameLength: 1 456 | VariableNaming: 457 | active: true 458 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 459 | variablePattern: '[a-z][A-Za-z0-9]*' 460 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' 461 | excludeClassPattern: '$^' 462 | ignoreOverridden: true 463 | 464 | performance: 465 | active: true 466 | ArrayPrimitive: 467 | active: true 468 | ForEachOnRange: 469 | active: true 470 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 471 | SpreadOperator: 472 | active: true 473 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 474 | UnnecessaryTemporaryInstantiation: 475 | active: true 476 | 477 | potential-bugs: 478 | active: true 479 | CastToNullableType: 480 | active: false 481 | Deprecation: 482 | active: false 483 | DontDowncastCollectionTypes: 484 | active: false 485 | DuplicateCaseInWhenExpression: 486 | active: true 487 | EqualsAlwaysReturnsTrueOrFalse: 488 | active: true 489 | EqualsWithHashCodeExist: 490 | active: true 491 | ExitOutsideMain: 492 | active: false 493 | ExplicitGarbageCollectionCall: 494 | active: true 495 | HasPlatformType: 496 | active: false 497 | IgnoredReturnValue: 498 | active: false 499 | restrictToAnnotatedMethods: true 500 | returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult'] 501 | ImplicitDefaultLocale: 502 | active: true 503 | ImplicitUnitReturnType: 504 | active: false 505 | allowExplicitReturnType: true 506 | InvalidRange: 507 | active: true 508 | IteratorHasNextCallsNextMethod: 509 | active: true 510 | IteratorNotThrowingNoSuchElementException: 511 | active: true 512 | LateinitUsage: 513 | active: false 514 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 515 | excludeAnnotatedProperties: [] 516 | ignoreOnClassesPattern: '' 517 | MapGetWithNotNullAssertionOperator: 518 | active: false 519 | MissingWhenCase: 520 | active: true 521 | allowElseExpression: true 522 | NullableToStringCall: 523 | active: false 524 | RedundantElseInWhen: 525 | active: true 526 | UnconditionalJumpStatementInLoop: 527 | active: false 528 | UnnecessaryNotNullOperator: 529 | active: true 530 | UnnecessarySafeCall: 531 | active: true 532 | UnreachableCatchBlock: 533 | active: false 534 | UnreachableCode: 535 | active: true 536 | UnsafeCallOnNullableType: 537 | active: true 538 | UnsafeCast: 539 | active: true 540 | UnusedUnaryOperator: 541 | active: false 542 | UselessPostfixExpression: 543 | active: false 544 | WrongEqualsTypeParameter: 545 | active: true 546 | 547 | style: 548 | active: true 549 | ClassOrdering: 550 | active: false 551 | CollapsibleIfStatements: 552 | active: false 553 | DataClassContainsFunctions: 554 | active: false 555 | conversionFunctionPrefix: 'to' 556 | DataClassShouldBeImmutable: 557 | active: false 558 | DestructuringDeclarationWithTooManyEntries: 559 | active: false 560 | maxDestructuringEntries: 3 561 | EqualsNullCall: 562 | active: true 563 | EqualsOnSignatureLine: 564 | active: false 565 | ExplicitCollectionElementAccessMethod: 566 | active: false 567 | ExplicitItLambdaParameter: 568 | active: false 569 | ExpressionBodySyntax: 570 | active: false 571 | includeLineWrapping: false 572 | ForbiddenComment: 573 | active: true 574 | values: ['TODO:', 'FIXME:', 'STOPSHIP:'] 575 | allowedPatterns: '' 576 | ForbiddenImport: 577 | active: false 578 | imports: [] 579 | forbiddenPatterns: '' 580 | ForbiddenMethodCall: 581 | active: false 582 | methods: ['kotlin.io.println', 'kotlin.io.print'] 583 | ForbiddenPublicDataClass: 584 | active: true 585 | excludes: ['**'] 586 | ignorePackages: ['*.internal', '*.internal.*'] 587 | ForbiddenVoid: 588 | active: false 589 | ignoreOverridden: false 590 | ignoreUsageInGenerics: false 591 | FunctionOnlyReturningConstant: 592 | active: true 593 | ignoreOverridableFunction: true 594 | ignoreActualFunction: true 595 | excludedFunctions: 'describeContents' 596 | excludeAnnotatedFunction: ['dagger.Provides'] 597 | LibraryCodeMustSpecifyReturnType: 598 | active: true 599 | excludes: ['**'] 600 | LibraryEntitiesShouldNotBePublic: 601 | active: true 602 | excludes: ['**'] 603 | LoopWithTooManyJumpStatements: 604 | active: true 605 | maxJumpCount: 1 606 | MagicNumber: 607 | active: true 608 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 609 | ignoreNumbers: ['-1', '0', '1', '2'] 610 | ignoreHashCodeFunction: true 611 | ignorePropertyDeclaration: false 612 | ignoreLocalVariableDeclaration: false 613 | ignoreConstantDeclaration: true 614 | ignoreCompanionObjectPropertyDeclaration: true 615 | ignoreAnnotation: false 616 | ignoreNamedArgument: true 617 | ignoreEnums: false 618 | ignoreRanges: false 619 | ignoreExtensionFunctions: true 620 | MandatoryBracesIfStatements: 621 | active: false 622 | MandatoryBracesLoops: 623 | active: false 624 | MaxLineLength: 625 | active: true 626 | maxLineLength: 120 627 | excludePackageStatements: true 628 | excludeImportStatements: true 629 | excludeCommentStatements: false 630 | MayBeConst: 631 | active: true 632 | ModifierOrder: 633 | active: true 634 | MultilineLambdaItParameter: 635 | active: false 636 | NestedClassesVisibility: 637 | active: true 638 | NewLineAtEndOfFile: 639 | active: true 640 | NoTabs: 641 | active: false 642 | OptionalAbstractKeyword: 643 | active: true 644 | OptionalUnit: 645 | active: false 646 | OptionalWhenBraces: 647 | active: false 648 | PreferToOverPairSyntax: 649 | active: false 650 | ProtectedMemberInFinalClass: 651 | active: true 652 | RedundantExplicitType: 653 | active: false 654 | RedundantHigherOrderMapUsage: 655 | active: false 656 | RedundantVisibilityModifierRule: 657 | active: false 658 | ReturnCount: 659 | active: true 660 | max: 2 661 | excludedFunctions: 'equals' 662 | excludeLabeled: false 663 | excludeReturnFromLambda: true 664 | excludeGuardClauses: false 665 | SafeCast: 666 | active: true 667 | SerialVersionUIDInSerializableClass: 668 | active: true 669 | SpacingBetweenPackageAndImports: 670 | active: false 671 | ThrowsCount: 672 | active: true 673 | max: 2 674 | TrailingWhitespace: 675 | active: false 676 | UnderscoresInNumericLiterals: 677 | active: false 678 | acceptableDecimalLength: 5 679 | UnnecessaryAbstractClass: 680 | active: true 681 | excludeAnnotatedClasses: ['dagger.Module'] 682 | UnnecessaryAnnotationUseSiteTarget: 683 | active: false 684 | UnnecessaryApply: 685 | active: true 686 | UnnecessaryFilter: 687 | active: false 688 | UnnecessaryInheritance: 689 | active: true 690 | UnnecessaryLet: 691 | active: false 692 | UnnecessaryParentheses: 693 | active: false 694 | UntilInsteadOfRangeTo: 695 | active: false 696 | UnusedImports: 697 | active: false 698 | UnusedPrivateClass: 699 | active: true 700 | UnusedPrivateMember: 701 | active: true 702 | allowedNames: '(_|ignored|expected|serialVersionUID)' 703 | UseArrayLiteralsInAnnotations: 704 | active: false 705 | UseCheckNotNull: 706 | active: false 707 | UseCheckOrError: 708 | active: false 709 | UseDataClass: 710 | active: false 711 | excludeAnnotatedClasses: [] 712 | allowVars: false 713 | UseEmptyCounterpart: 714 | active: false 715 | UseIfEmptyOrIfBlank: 716 | active: false 717 | UseIfInsteadOfWhen: 718 | active: false 719 | UseIsNullOrEmpty: 720 | active: false 721 | UseOrEmpty: 722 | active: false 723 | UseRequire: 724 | active: false 725 | UseRequireNotNull: 726 | active: false 727 | UselessCallOnNotNull: 728 | active: true 729 | UtilityClassWithPublicConstructor: 730 | active: true 731 | VarCouldBeVal: 732 | active: true 733 | WildcardImport: 734 | active: true 735 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 736 | excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*'] --------------------------------------------------------------------------------