Compare commits
107 Commits
feature/au
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
5e0fce2a60 | |
|
|
a070a94381 | |
|
|
0976db1e47 | |
|
|
e17454b996 | |
|
|
c8ed908656 | |
|
|
20f07a4a9d | |
|
|
a85e655aba | |
|
|
3c837cf1d1 | |
|
|
0fda706e9f | |
|
|
9281a5d213 | |
|
|
844c0a9c73 | |
|
|
d2ddf042e6 | |
|
|
8da83cea86 | |
|
|
8e08f8a570 | |
|
|
7710442654 | |
|
|
171cacf30d | |
|
|
723e5a4178 | |
|
|
faf1e92633 | |
|
|
eef1504171 | |
|
|
1d63ab6156 | |
|
|
709c71e48e | |
|
|
cd9e12f6d9 | |
|
|
628c992b8d | |
|
|
1a069b8b6f | |
|
|
ac9cac3049 | |
|
|
1c5ef179ef | |
|
|
eeaa02e7ea | |
|
|
5109c001cc | |
|
|
ea86f97485 | |
|
|
68f97ca05a | |
|
|
b668819aa2 | |
|
|
be1ef45fb2 | |
|
|
345db02feb | |
|
|
ae3b8b109e | |
|
|
be1d99da35 | |
|
|
a144f06bb8 | |
|
|
0f83486593 | |
|
|
720ecb8788 | |
|
|
bc8d9cb20d | |
|
|
40254bfd58 | |
|
|
9bf9ae2695 | |
|
|
144671a392 | |
|
|
62664f893a | |
|
|
8be6b184aa | |
|
|
48ab57e96d | |
|
|
d55d9bd600 | |
|
|
735a71c9cd | |
|
|
d176e846b7 | |
|
|
423d4213f5 | |
|
|
a3d92210b9 | |
|
|
ec5d050c5d | |
|
|
5472e9dfdf | |
|
|
dfa3a75f4c | |
|
|
7313b43dff | |
|
|
2a3561135b | |
|
|
b1780329ea | |
|
|
3898a48095 | |
|
|
7c73d2418c | |
|
|
195d2f16c9 | |
|
|
153aaf7608 | |
|
|
91d4dd66da | |
|
|
a95b1ac4fd | |
|
|
e9a8c86515 | |
|
|
89c0b4afd1 | |
|
|
aa2451e12a | |
|
|
d684b13f37 | |
|
|
3a284c8905 | |
|
|
6ea1bf2b65 | |
|
|
de1778463a | |
|
|
7d555d00b2 | |
|
|
8d5719fa1e | |
|
|
80bc2e4dfa | |
|
|
6810db34d1 | |
|
|
ba35eb76c2 | |
|
|
8463eea777 | |
|
|
6dd6be5a91 | |
|
|
a54af3cef4 | |
|
|
1f5b98b80d | |
|
|
2eeab2dade | |
|
|
0de74ca3a8 | |
|
|
2d75a4a991 | |
|
|
4bc0a10106 | |
|
|
8c6b6a29aa | |
|
|
1dde5efd61 | |
|
|
dcc35b3b10 | |
|
|
915dbe9301 | |
|
|
bf53ebf369 | |
|
|
7366a93808 | |
|
|
b4ec699e3b | |
|
|
8e9924afb7 | |
|
|
899b91edb6 | |
|
|
c50bf4df50 | |
|
|
4f61e105a7 | |
|
|
79ae4b8b65 | |
|
|
facff6525d | |
|
|
be38ebf451 | |
|
|
4f742e9235 | |
|
|
5daf28ac10 | |
|
|
6a08c4568b | |
|
|
11fcb7a586 | |
|
|
fd4c32042c | |
|
|
131c7bd573 | |
|
|
347ff03670 | |
|
|
0c39aedea9 | |
|
|
67adec4d92 | |
|
|
5e84eb2bb3 | |
|
|
6f703d0bfc |
328
README.md
328
README.md
|
|
@ -19,6 +19,19 @@
|
|||
implementation("ru.touchin:common")
|
||||
}
|
||||
```
|
||||
|
||||
## Project Gradle tasks
|
||||
### Detekt:
|
||||
- `gradle $project:detekt` - detect not formatted, complex code.
|
||||
|
||||
Reports are stored in "$pwd/build/reports/kotlin-detekt-${project.name}.html".
|
||||
|
||||
### DiKTat:
|
||||
- `gradle :diktatCheck` - detect not formatted code of "kt", "kts" files;
|
||||
- `gradle :diktatFix` - if possible, fix not formatted code. Known issues: full fix may require 1+ launch in order to apply all rules; some rules potentially may break code syntax.
|
||||
|
||||
By setting environment variable `TASKS_FILE_REPORT_ENABLED`(true, false) you may configure raw console output or html file as report.
|
||||
Reports are stored in "$pwd/build/reports/diktat-report.html".
|
||||
|
||||
## common
|
||||
|
||||
|
|
@ -68,6 +81,106 @@
|
|||
|
||||
Утилиты для тестирования репозиториев
|
||||
|
||||
## codestyle-archunit
|
||||
|
||||
Набор правил для поддержки оформления архитектуры.
|
||||
|
||||
#### Список доступных правил
|
||||
- `ru.touchin.codestyle.archunit.rules.ClassNamingArchRules`
|
||||
- `ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules`
|
||||
|
||||
|
||||
#### Gradle plugin
|
||||
Настройка и применение совместно с [ArchUnit Gradle Plugin](https://github.com/societe-generale/arch-unit-gradle-plugin).
|
||||
|
||||
Действие `checkRules` для проверки соответствие правилам запускается при операциях сборки, по умолчанию.
|
||||
Вручную можно вызвать командой ``gradle :checkRules`` для нужного модуля.
|
||||
Добавить его можно следующим образом на примере установки в рутовый gradle.build проекта:
|
||||
|
||||
Groovy DSL:
|
||||
```groovy
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "com.societegenerale.commons:arch-unit-gradle-plugin:3.0.0"
|
||||
}
|
||||
}
|
||||
subprojects {
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependency "com.tngtech.archunit:archunit:1.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "java"
|
||||
apply plugin: "com.societegenerale.commons.arch-unit-gradle-plugin"
|
||||
|
||||
archUnit {
|
||||
mainScopePath = "/classes/kotlin/main" // or "/classes/java/main"
|
||||
testScopePath = "/classes/kotlin/test" // or "/classes/java/test"
|
||||
|
||||
var applyType = applyOn("ru.touchin", "main")
|
||||
configurableRules = [
|
||||
configurableRule(
|
||||
"ru.touchin.codestyle.archunit.rules.ClassNamingArchRules",
|
||||
applyType,
|
||||
),
|
||||
configurableRule(
|
||||
"ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules",
|
||||
applyType,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
archUnitExtraLib "ru.touchin:codestyle-archunit" // or archUnitExtraLib project(":codestyle-archunit")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Kotlin DSL:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.societegenerale.commons.arch-unit-gradle-plugin") version "3.0.0"
|
||||
}
|
||||
subprojects {
|
||||
configure<DependencyManagementExtension> {
|
||||
dependencies {
|
||||
dependency("com.tngtech.archunit:archunit:1.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "com.societegenerale.commons.arch-unit-gradle-plugin")
|
||||
|
||||
archUnit {
|
||||
mainScopePath = "/classes/kotlin/main" // or "/classes/java/main"
|
||||
testScopePath = "/classes/kotlin/test" // or "/classes/java/test"
|
||||
|
||||
configurableRules = listOf(
|
||||
"ru.touchin.codestyle.archunit.rules.ClassNamingArchRules",
|
||||
"ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules"
|
||||
).map { package ->
|
||||
configurableRule(
|
||||
package,
|
||||
applyOn("ru.touchin", "main")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
archUnitExtraLib("ru.touchin:codestyle-archunit") // or archUnitExtraLib(project(":codestyle-archunit"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Отключить проверки на таске помимо конфигурирования `configurableRule` можно также таким образом:
|
||||
```kotlin
|
||||
// clear action launch for root project to avoid exception
|
||||
tasks.checkRules.configure {
|
||||
actions.clear()
|
||||
}
|
||||
```
|
||||
|
||||
## logger
|
||||
|
||||
Основные компоненты логирования:
|
||||
|
|
@ -89,11 +202,17 @@ Interceptor для логирования запросов/ответов.
|
|||
|
||||
## exception-handler-spring-web
|
||||
|
||||
Перехватывает ошибки сервера, определяет код ошибки и возвращает их в правильный `response`
|
||||
Перехватывает ошибки сервера, определяет код ошибки и возвращает их в правильный `response`.
|
||||
Подключается с помощью аннотации `@EnableSpringExceptionHandler`
|
||||
|
||||
## exception-handler-logger-spring-web
|
||||
|
||||
Добавляет логирование в обработку ошибок
|
||||
Добавляет логирование в обработку ошибок.
|
||||
Подключается с помощью аннотации `@EnableSpringExceptionHandlerLogger` до подключения основного модуля.
|
||||
|
||||
## validation-spring
|
||||
|
||||
Добавляет аннотации для валидации запросов.
|
||||
|
||||
## version-spring-web
|
||||
|
||||
|
|
@ -123,11 +242,11 @@ Interceptor для логирования запросов/ответов.
|
|||
|
||||
Модуль для хранения настроек
|
||||
|
||||
## auth-core
|
||||
## security-authorization-server-core
|
||||
|
||||
Модуль авторизации
|
||||
|
||||
## auth-jwt-core
|
||||
## security-authorization-server-jwt-core
|
||||
|
||||
Добавляет поддержку jwt-токенов (создание/хранение). Для работы этого модуля требуется прописать в пропертях:
|
||||
|
||||
|
|
@ -148,3 +267,204 @@ token.refresh:
|
|||
prefix: RT-
|
||||
timeToLive: PT2H # 2 hours
|
||||
```
|
||||
|
||||
Генерация ключей:
|
||||
|
||||
```bash
|
||||
openssl genrsa -out private.pem 4096
|
||||
openssl rsa -in private.pem -pubout -out public.pem
|
||||
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt
|
||||
|
||||
cat private_key.pem
|
||||
cat public.pem
|
||||
```
|
||||
|
||||
## security-authorization-server-oauth2-metadata
|
||||
|
||||
OAuth2 metadata support.
|
||||
|
||||
## security-jwt-common
|
||||
|
||||
JWT related utilities.
|
||||
|
||||
## security-resource-server-default-configuration
|
||||
|
||||
Default configuration for the Spring OAuth2 resource server with JWT auth.
|
||||
|
||||
## security-resource-server-custom-configuration
|
||||
|
||||
Custom configuration for the Spring OAuth2 resource server with JWT auth. Requires the following properties:
|
||||
|
||||
``` yaml
|
||||
token.access:
|
||||
issuer: ${app.issuer}
|
||||
signatureAlgorithm: RS256
|
||||
keyPair:
|
||||
public: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
-----END PUBLIC KEY-----
|
||||
```
|
||||
|
||||
## security-resource-server-test-configuration
|
||||
|
||||
Disables Spring OAuth2 resource server for testing.
|
||||
|
||||
## s3-storage
|
||||
|
||||
Amazon S3 support.
|
||||
|
||||
## server-info-spring-web
|
||||
|
||||
Allow include headers with information about the server in responses
|
||||
|
||||
To get started you need:
|
||||
1) Add annotation to configuration
|
||||
2) Add property to yml/properties file:
|
||||
```
|
||||
server.info:
|
||||
buildVersion: ${buildVersion}
|
||||
```
|
||||
3) Implement ServerInfoService (optional. If you want to add other headers)
|
||||
4) Add dir with impl ServerInfoService in ComponentScan annotation
|
||||
|
||||
## push-message-provider
|
||||
|
||||
Интерфейсы и компоненты для модулей по обеспечению интеграции с сервисами отправки пуш-уведомлений. Является необходимой зависимостью для использования провайдеров.
|
||||
|
||||
Далее рассматривается пример использования подключаемых модулей-провайдеров.
|
||||
``` kotlin
|
||||
@Service
|
||||
class PushSendingService(
|
||||
private val pushMessageProviderServiceFactory: PushMessageProviderServiceFactory
|
||||
) {
|
||||
|
||||
fun sendPushMessage() {
|
||||
val yourPushToken = "pushTokenForChecking"
|
||||
val platform = PlatformType.ANDROID_GOOGLE
|
||||
|
||||
val pushMessageProvider: PushMessageProviderService = pushMessageProviderServiceFactory.get(platform)
|
||||
|
||||
val result = pushMessageProvider.check( // Проверка валидности токена для обозначения целесообразности отправки
|
||||
PushTokenCheck(
|
||||
pushToken = yourPushToken
|
||||
)
|
||||
)
|
||||
|
||||
if (result.status == PushTokenStatus.VALID) { // Токен валиден, PushMessageProviderService интегрирован в систему
|
||||
// Отправка пуш-уведомления
|
||||
pushMessageProvider.send(
|
||||
PushTokenMessage(
|
||||
token = yourPushToken,
|
||||
pushMessageNotification = PushMessageNotification(
|
||||
title = "Your PushMessage",
|
||||
description = "Provided by PushMessageProviderService",
|
||||
imageUrl = null
|
||||
),
|
||||
data = mapOf(
|
||||
"customKey" to "customData"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## push-message-provider-fcm
|
||||
|
||||
Модуль по обеспечению интеграции с Firebase Cloud Messaging.
|
||||
1) Подключение компонентов Spring осуществляется при помощи аннотации `@EnablePushMessageProviderFcm`.
|
||||
2) Необходимо добавление конфигурации для модуля с выбранным способом хранения данных для авторизации. Пример файла конфигурации в формате yaml:
|
||||
``` yaml
|
||||
push-message-provider:
|
||||
platformProviders:
|
||||
ANDROID_GOOGLE:
|
||||
- FCM
|
||||
IOS:
|
||||
- FCM
|
||||
fcm:
|
||||
appName: yourAppName
|
||||
auth:
|
||||
# Выбранный тип авторизации
|
||||
client:
|
||||
readTimeout: 10s
|
||||
connectionTimeout: 1s
|
||||
```
|
||||
3) Настраивается способ предоставления авторизации для Firebase Cloud Messaging.
|
||||
|
||||
А) Токен доступа из консоли Google, добавляемый в конфигурацию настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
token:
|
||||
value: testValue
|
||||
expiresAt: 2023-01-01 23:59:59 +00:00
|
||||
```
|
||||
B) Данные в файле из консоли Firebase, добавляемые в resources с обозначением пути в конфигурации настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
credentialsFile:
|
||||
path: credentials/firebase-admin.json
|
||||
```
|
||||
C) Данные из файла консоли Firebase, добавляемые в конфигурацию настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
credentialsData:
|
||||
type: service_account
|
||||
projectId: yourProjectId
|
||||
privateKeyId: yourPrivateKeyId
|
||||
privateKey: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
-----END PRIVATE KEY-----
|
||||
clientEmail: yourClientEmail
|
||||
clientId: yourClientId
|
||||
authUri: yourAuthUri
|
||||
tokenUri: yourTokenUri
|
||||
authProviderX509CertUrl: yourAuthProviderX509CertUrl
|
||||
clientX509CertUrl: yourClientX509CertUrl
|
||||
```
|
||||
|
||||
## push-message-provider-hpk
|
||||
|
||||
Модуль по обеспечению интеграции с Huawei Push Kit.
|
||||
|
||||
1) Подключение нового провайдера осуществляется при помощи аннотации `@EnablePushMessageProviderHpk`.
|
||||
2) Для логирования запросов к сервису HPK нужно встроить в контейнер Spring собственный `WebClientLogger` из модуля `logger-spring-web` или же использовать стандартный посредством импорта конфигурации:
|
||||
``` kotlin
|
||||
@Import(
|
||||
SpringLoggerConfiguration::class,
|
||||
SpringLoggerWebConfiguration::class
|
||||
)
|
||||
class YourConfiguration
|
||||
```
|
||||
3) Нужно добавить конфигурацию для считывания модулем. Пример файла в формате yaml:
|
||||
``` yaml
|
||||
push-message-provider:
|
||||
platformProviders:
|
||||
ANDROID_HUAWEI:
|
||||
- HPK
|
||||
hpk:
|
||||
web-services:
|
||||
client-id: yourClientId
|
||||
oauth:
|
||||
client-secret: yourClientSecret
|
||||
url: https://oauth-login.cloud.huawei.com/oauth2/v3/
|
||||
http:
|
||||
connection-timeout: 1s
|
||||
read-timeout: 10s
|
||||
write-timeout: 10s
|
||||
ssl: # Опциональная структура
|
||||
handshake-timeout: 1s
|
||||
notify-read-timeout: 1s
|
||||
notify-flush-timeout: 1s
|
||||
hpk:
|
||||
url: https://push-api.cloud.huawei.com/v1/
|
||||
http:
|
||||
connection-timeout: 1s
|
||||
read-timeout: 10s
|
||||
write-timeout: 10s
|
||||
ssl: # Опциональная структура
|
||||
handshake-timeout: 1s
|
||||
notify-read-timeout: 1s
|
||||
notify-flush-timeout: 1s
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.auth.core.configurations
|
||||
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
|
||||
import ru.touchin.common.spring.jpa.EnableJpaAuditingExtra
|
||||
|
||||
@EntityScan("ru.touchin.auth.core")
|
||||
@EnableJpaRepositories("ru.touchin.auth.core")
|
||||
@EnableCaching
|
||||
@EnableJpaAuditingExtra
|
||||
class AuthCoreDatabaseConfiguration
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package ru.touchin.auth.core.device.dto.enums
|
||||
|
||||
enum class DevicePlatform {
|
||||
Android, Huawei, Apple
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package ru.touchin.auth.core.scope.models
|
||||
|
||||
import ru.touchin.common.spring.jpa.models.BaseEntity
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Table
|
||||
|
||||
@Entity
|
||||
@Table(name = "scopes")
|
||||
class ScopeEntity : BaseEntity() {
|
||||
|
||||
@Id
|
||||
lateinit var name: String
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
|
||||
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id ("org.springframework.boot") apply false
|
||||
id("org.springframework.boot") apply false
|
||||
|
||||
// IntelliJ
|
||||
idea
|
||||
|
|
@ -15,6 +17,9 @@ plugins {
|
|||
// A Gradle plugin that provides Maven-like dependency management and exclusions
|
||||
// https://docs.spring.io/dependency-management-plugin/docs/current/reference/html/
|
||||
id("io.spring.dependency-management")
|
||||
|
||||
id("io.gitlab.arturbosch.detekt")
|
||||
id("org.cqfn.diktat.diktat-gradle-plugin")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
@ -30,6 +35,30 @@ allprojects {
|
|||
apply(plugin = "idea")
|
||||
}
|
||||
|
||||
diktat {
|
||||
inputs {
|
||||
include(
|
||||
"**/src/**/*.kt",
|
||||
"*.kts",
|
||||
"**/*.kts",
|
||||
"**/src/**/*.kts",
|
||||
)
|
||||
exclude(
|
||||
"**/build/**",
|
||||
"build/**",
|
||||
)
|
||||
}
|
||||
val tasksFileReportEnabled = project.properties["TASKS_FILE_REPORT_ENABLED"]
|
||||
?.let { it.toString().toBooleanLenient() }
|
||||
?: true
|
||||
|
||||
reporter = if (tasksFileReportEnabled) "html" else "plain"
|
||||
output = if (tasksFileReportEnabled) "${project.buildDir}/reports/diktat-report.html" else String()
|
||||
diktatConfigFile = file("$rootDir/diktat-analysis.yml")
|
||||
ignoreFailures = true
|
||||
debug = false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
println("Enabling Kotlin JVM plugin in project ${project.name}...")
|
||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||
|
|
@ -40,6 +69,26 @@ subprojects {
|
|||
println("Enabling Spring Boot Dependency Management in project ${project.name}...")
|
||||
apply(plugin = "io.spring.dependency-management")
|
||||
|
||||
println("Enabling Detekt support in project ${project.name}...")
|
||||
apply(plugin = "io.gitlab.arturbosch.detekt")
|
||||
|
||||
detekt {
|
||||
config = files("$rootDir/detekt-config.yml")
|
||||
source = files(
|
||||
DetektExtension.Companion.DEFAULT_SRC_DIR_JAVA,
|
||||
DetektExtension.Companion.DEFAULT_SRC_DIR_KOTLIN,
|
||||
)
|
||||
reports {
|
||||
txt.enabled = false
|
||||
xml.enabled = false
|
||||
html {
|
||||
enabled = true
|
||||
destination = file("${project.buildDir}/reports/kotlin-detekt-${project.name}.html")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
configure<DependencyManagementExtension> {
|
||||
imports {
|
||||
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
|
||||
|
|
@ -50,13 +99,17 @@ subprojects {
|
|||
dependency("ch.qos.logback.contrib:logback-json-classic:0.1.5")
|
||||
dependency("ch.qos.logback.contrib:logback-jackson:0.1.5")
|
||||
|
||||
dependency("org.testcontainers:testcontainers:1.15.1")
|
||||
dependency("org.testcontainers:postgresql:1.15.1")
|
||||
dependency("org.testcontainers:junit-jupiter:1.15.1")
|
||||
dependency("org.testcontainers:testcontainers:1.18.3")
|
||||
dependency("org.testcontainers:postgresql:1.18.3")
|
||||
dependency("org.testcontainers:junit-jupiter:1.18.3")
|
||||
dependency("org.junit.jupiter:junit-jupiter-api:5.4.2")
|
||||
dependency("org.junit.jupiter:junit-jupiter-params:5.4.2")
|
||||
dependency("org.junit.jupiter:junit-jupiter-engine:5.4.2")
|
||||
|
||||
dependency("com.tngtech.archunit:archunit:1.0.1")
|
||||
|
||||
dependency("org.liquibase:liquibase-core:4.4.0")
|
||||
|
||||
dependency("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
dependency("org.mockito:mockito-inline:3.11.0")
|
||||
|
||||
|
|
@ -68,13 +121,19 @@ subprojects {
|
|||
dependency("org.locationtech.spatial4j:spatial4j:0.8")
|
||||
|
||||
dependency("com.auth0:java-jwt:3.10.3")
|
||||
|
||||
dependency("software.amazon.awssdk:s3:2.10.11")
|
||||
|
||||
dependency("com.google.firebase:firebase-admin:9.0.0")
|
||||
|
||||
dependency("com.github.ua-parser:uap-java:1.5.3")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// use for @ConstructorBinding
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation(project(":common-spring-web"))
|
||||
|
||||
implementation(project(":logger-spring"))
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
implementation("org.springframework.boot:spring-boot-starter-aop")
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.captcha.annotations
|
||||
|
||||
import ru.touchin.captcha.dto.enums.CaptchaScore
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Captcha(
|
||||
val action: String,
|
||||
val minScore: CaptchaScore = CaptchaScore.AVERAGE
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package ru.touchin.captcha.aspects
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.context.request.RequestContextHolder
|
||||
import org.springframework.web.context.request.ServletRequestAttributes
|
||||
import ru.touchin.captcha.annotations.Captcha
|
||||
import ru.touchin.captcha.exceptions.CaptchaResponseMissingException
|
||||
import ru.touchin.captcha.services.CaptchaService
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "captcha", name = ["enabled"], havingValue = "true", matchIfMissing = true)
|
||||
class CaptchaSiteVerifyAspect(private val captchaService: CaptchaService) {
|
||||
|
||||
@Throws(Throwable::class)
|
||||
@Before("@annotation(captcha)")
|
||||
fun captchaSiteVerify(captcha: Captcha) {
|
||||
val currentRequestAttributes = RequestContextHolder.currentRequestAttributes()
|
||||
as? ServletRequestAttributes
|
||||
?: throw IllegalStateException("unable to get current request attributes")
|
||||
|
||||
val captchaResponse = currentRequestAttributes.request.getHeader(CAPTCHA_RESPONSE_HEADER_NAME)
|
||||
?: throw CaptchaResponseMissingException()
|
||||
|
||||
captchaService.verify(captchaResponse)
|
||||
.validateAction(captcha.action)
|
||||
.validateScore(captcha.minScore.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CAPTCHA_RESPONSE_HEADER_NAME = "X-Captcha-Response"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.captcha.configurations
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
|
||||
@ComponentScan("ru.touchin.captcha")
|
||||
@ConfigurationPropertiesScan("ru.touchin.captcha")
|
||||
class CaptchaConfiguration
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.touchin.captcha.dto
|
||||
|
||||
import ru.touchin.captcha.exceptions.CaptchaActionMismatchException
|
||||
import ru.touchin.captcha.exceptions.CaptchaScoreBelowMinimumException
|
||||
|
||||
data class CaptchaVerificationResult(val score: Double, val action: String) {
|
||||
|
||||
fun validateScore(minScore: Double): CaptchaVerificationResult {
|
||||
if (score < minScore) {
|
||||
throw CaptchaScoreBelowMinimumException(score)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun validateAction(actionToValidate: String): CaptchaVerificationResult {
|
||||
if (action != actionToValidate) {
|
||||
throw CaptchaActionMismatchException(actionToValidate)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.captcha.dto.enums
|
||||
|
||||
enum class CaptchaScore(val value: Double) {
|
||||
|
||||
WEAK(0.2),
|
||||
AVERAGE(0.5),
|
||||
STRONG(0.8)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.captcha.dto.response
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
data class CaptchaSiteVerifyResponse(
|
||||
val score: Double,
|
||||
val action: String,
|
||||
val success: Boolean,
|
||||
@JsonProperty("challenge_ts")
|
||||
val challengeTs: ZonedDateTime
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.captcha.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
class CaptchaActionMismatchException(
|
||||
action: String
|
||||
) : CommonException("invalid captcha action $action")
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.touchin.captcha.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
class CaptchaResponseMissingException : CommonException("missing captcha response header")
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.captcha.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
class CaptchaScoreBelowMinimumException(
|
||||
score: Double,
|
||||
) : CommonException("captcha score below minimum $score")
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.captcha.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
class CaptchaSiteVerifyFailureException(
|
||||
description: String?
|
||||
) : CommonException(description)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.captcha.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
class CaptchaUnknownActionException(
|
||||
action: String
|
||||
) : CommonException("unknown captcha action $action")
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.captcha.properties
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.ConstructorBinding
|
||||
import java.net.URI
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "captcha")
|
||||
data class CaptchaProperties(
|
||||
val uri: URI,
|
||||
val secret: String,
|
||||
val actions: List<String>
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.captcha.services
|
||||
|
||||
import ru.touchin.captcha.dto.CaptchaVerificationResult
|
||||
|
||||
interface CaptchaService {
|
||||
|
||||
fun verify(response: String): CaptchaVerificationResult
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package ru.touchin.captcha.services.impl
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
import ru.touchin.captcha.dto.CaptchaVerificationResult
|
||||
import ru.touchin.captcha.exceptions.CaptchaUnknownActionException
|
||||
import ru.touchin.captcha.properties.CaptchaProperties
|
||||
import ru.touchin.captcha.services.CaptchaService
|
||||
import ru.touchin.captcha.webclients.CaptchaWebClient
|
||||
import ru.touchin.logger.spring.annotations.AutoLogging
|
||||
import ru.touchin.logger.spring.annotations.LogValue
|
||||
|
||||
@Service
|
||||
class CaptchaServiceImpl(
|
||||
private val captchaWebClient: CaptchaWebClient,
|
||||
private val captchaProperties: CaptchaProperties,
|
||||
) : CaptchaService {
|
||||
|
||||
@LogValue
|
||||
@AutoLogging(tags = ["CAPTCHA", "CAPTCHA_VERIFICATION"])
|
||||
override fun verify(response: String): CaptchaVerificationResult {
|
||||
val siteVerifyResponse = captchaWebClient.siteVerify(response)
|
||||
|
||||
if (siteVerifyResponse.action !in captchaProperties.actions) {
|
||||
throw CaptchaUnknownActionException(siteVerifyResponse.action)
|
||||
}
|
||||
|
||||
return CaptchaVerificationResult(
|
||||
score = siteVerifyResponse.score,
|
||||
action = siteVerifyResponse.action,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package ru.touchin.captcha.webclients
|
||||
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import ru.touchin.captcha.properties.CaptchaProperties
|
||||
import ru.touchin.captcha.dto.response.CaptchaSiteVerifyResponse
|
||||
import ru.touchin.captcha.exceptions.CaptchaSiteVerifyFailureException
|
||||
import ru.touchin.common.spring.web.webclient.BaseLogWebClient
|
||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
||||
|
||||
private const val SITE_VERIFY_PATH = "/recaptcha/api/siteverify"
|
||||
|
||||
@Component
|
||||
class CaptchaWebClient(
|
||||
webClientLogger: WebClientLogger,
|
||||
webClientBuilder: WebClient.Builder,
|
||||
private val captchaProperties: CaptchaProperties,
|
||||
) : BaseLogWebClient(webClientLogger, webClientBuilder) {
|
||||
|
||||
override fun getWebClient(): WebClient {
|
||||
return getWebClientBuilder(captchaProperties.uri.toString()).build()
|
||||
}
|
||||
|
||||
fun siteVerify(response: String): CaptchaSiteVerifyResponse {
|
||||
return getWebClient().post()
|
||||
.uri {
|
||||
it.path(SITE_VERIFY_PATH)
|
||||
it.queryParam("response", response)
|
||||
it.queryParam("secret", captchaProperties.secret)
|
||||
|
||||
it.build()
|
||||
}
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange(
|
||||
clazz = CaptchaSiteVerifyResponse::class.java,
|
||||
requestLogData = RequestLogData(
|
||||
uri = SITE_VERIFY_PATH,
|
||||
logTags = listOf("CAPTCHA_SITEVERIFY"),
|
||||
method = HttpMethod.POST,
|
||||
)
|
||||
)
|
||||
.block()!!
|
||||
.also(this::validateSuccess)
|
||||
}
|
||||
|
||||
private fun validateSuccess(captchaSiteVerifyResponse: CaptchaSiteVerifyResponse) {
|
||||
if (!captchaSiteVerifyResponse.success) {
|
||||
throw CaptchaSiteVerifyFailureException("Captcha siteverify request did not succeed")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation("com.tngtech.archunit:archunit")
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package ru.touchin.codestyle.archunit.rules
|
||||
|
||||
import com.tngtech.archunit.lang.ArchRule
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods
|
||||
import ru.touchin.common.exceptions.CommonException
|
||||
|
||||
/**
|
||||
* Set of rules that defines constraint for classes naming.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object ClassNamingArchRules {
|
||||
|
||||
|
||||
val CLASSES_WHICH_HAVE_SCHEDULED_METHODS_MUST_HAVE_SUFFIX: ArchRule = methods()
|
||||
.that()
|
||||
.areAnnotatedWith("org.springframework.scheduling.annotation.Scheduled")
|
||||
.should().beDeclaredInClassesThat().haveSimpleNameEndingWith("Job")
|
||||
.allowEmptyShould(true)
|
||||
.because("Classes that use 'Scheduled' annotation must have name with 'Job' suffix")
|
||||
|
||||
val ANNOTATED_SERVICE_CLASSES_MUST_HAVE_SUFFIX: ArchRule = methods()
|
||||
.that().areAnnotatedWith("org.springframework.stereotype.Service")
|
||||
.should().beDeclaredInClassesThat().haveSimpleNameContaining("Service")
|
||||
.allowEmptyShould(true)
|
||||
.because("Classes that use 'Service' annotation are assignable to class with `Service` suffix")
|
||||
|
||||
val ANNOTATED_ENTITY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
||||
.that().areAnnotatedWith("javax.persistence.Entity")
|
||||
.should().haveSimpleNameEndingWith("Entity")
|
||||
.allowEmptyShould(true)
|
||||
.because("Hibernate 'Entity' classes must have name with 'Entity' suffix")
|
||||
|
||||
val EXCEPTION_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
||||
.that().areAssignableTo(Exception::class.java)
|
||||
.or().areAssignableTo(CommonException::class.java)
|
||||
.should().haveSimpleNameEndingWith("Exception")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Exception' classes must have name with 'Exception' suffix")
|
||||
|
||||
val JPAREPOSITORY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
||||
.that().areAssignableTo("org.springframework.data.jpa.repository.JpaRepository")
|
||||
.should().haveSimpleNameEndingWith("Repository")
|
||||
.allowEmptyShould(true)
|
||||
.because("Classes that use 'JpaRepository' must have name with 'Repository' suffix")
|
||||
|
||||
val WEBCLIENT_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
||||
.that().haveFullyQualifiedName("org.springframework.web.reactive.valction.client.WebClient")
|
||||
.should().onlyBeAccessed().byClassesThat().haveSimpleNameEndingWith("WebClient")
|
||||
.allowEmptyShould(true)
|
||||
.because("Classes that use Spring 'WebClient' must have name with 'WebClient' suffix")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package ru.touchin.codestyle.archunit.rules
|
||||
|
||||
import com.tngtech.archunit.lang.ArchRule
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
|
||||
|
||||
/**
|
||||
* Set of rules that defines constraint for classes placement.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object ClassPackagingArchRules {
|
||||
|
||||
|
||||
val IMPL_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("Impl")
|
||||
.should().resideInAPackage("..impl..")
|
||||
.allowEmptyShould(true)
|
||||
.because("Implementations of interfaces must reside in package 'impl'")
|
||||
|
||||
|
||||
val ENTITIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("Entity")
|
||||
.should().resideInAPackage("..models..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Entity' must reside in package 'models'")
|
||||
|
||||
val REPOSITORIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("Repository")
|
||||
.should().resideInAPackage("..repositories..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Repository' must reside in package 'repositories'")
|
||||
|
||||
val CORE_SERVICES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("CoreService")
|
||||
.should().resideInAPackage("..services..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'CoreService' must reside in package 'services'")
|
||||
|
||||
val CONVERTERS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("Converter")
|
||||
.should().resideInAPackage("..converters..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Converter' must reside in package 'converters'")
|
||||
|
||||
val EXCEPTIONS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().haveSimpleNameEndingWith("Exception")
|
||||
.should().resideInAPackage("..exceptions..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Exception' must reside in package 'exceptions'")
|
||||
|
||||
val ENUM_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
||||
.that().areEnums()
|
||||
.should().resideInAPackage("..enums..")
|
||||
.allowEmptyShould(true)
|
||||
.because("'Enum' must reside in package 'enums'")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.touchin.common.devices.enums
|
||||
|
||||
enum class DevicePlatform {
|
||||
Android, Huawei, Apple, Web
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@file:Suppress("unused")
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package ru.touchin.common.measure
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.common.messaging
|
||||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
interface BaseEvent {
|
||||
|
||||
val firedAt: ZonedDateTime
|
||||
|
||||
}
|
||||
|
|
@ -2,11 +2,15 @@ plugins {
|
|||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
id("maven-publish")
|
||||
id("org.jetbrains.kotlin.kapt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt("org.hibernate:hibernate-jpamodelgen")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation(project(":common"))
|
||||
implementation(project(":common-spring"))
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.common.spring.jpa.converters
|
||||
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import ru.touchin.common.page.GetPage
|
||||
|
||||
fun GetPage.toPageable(sort: Sort? = null): Pageable {
|
||||
val pageRequest = PageRequest.of(
|
||||
page - 1,
|
||||
size,
|
||||
)
|
||||
|
||||
if (sort != null) {
|
||||
return pageRequest.withSort(sort)
|
||||
}
|
||||
|
||||
return pageRequest
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ import liquibase.Liquibase
|
|||
import liquibase.database.Database
|
||||
import liquibase.database.DatabaseFactory
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import liquibase.resource.ClassLoaderResourceAccessor
|
||||
import liquibase.integration.spring.SpringResourceAccessor
|
||||
import org.springframework.core.io.DefaultResourceLoader
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.annotations.RunOnceOnStartup
|
||||
import javax.sql.DataSource
|
||||
|
|
@ -15,19 +16,25 @@ import javax.sql.DataSource
|
|||
@Component
|
||||
class LiquibaseStart(
|
||||
private val dataSource: DataSource,
|
||||
private val liquibaseParams: LiquibaseParams,
|
||||
private val liquibaseParams: List<LiquibaseParams>,
|
||||
) {
|
||||
|
||||
@RunOnceOnStartup
|
||||
fun runLiquibase() {
|
||||
dataSource.connection.use { connection ->
|
||||
val database: Database = DatabaseFactory.getInstance()
|
||||
.findCorrectDatabaseImplementation(JdbcConnection(connection))
|
||||
.apply { defaultSchemaName = liquibaseParams.schema }
|
||||
liquibaseParams.forEach { params ->
|
||||
dataSource.connection.use { connection ->
|
||||
val database: Database = DatabaseFactory.getInstance()
|
||||
.findCorrectDatabaseImplementation(JdbcConnection(connection))
|
||||
.apply {
|
||||
defaultSchemaName = params.schema
|
||||
}
|
||||
|
||||
val liquibase = Liquibase(liquibaseParams.changeLogPath, ClassLoaderResourceAccessor(), database)
|
||||
val resourceAccessor = SpringResourceAccessor(DefaultResourceLoader())
|
||||
|
||||
liquibase.update(Contexts())
|
||||
val liquibase = Liquibase(params.changeLogPath, resourceAccessor, database)
|
||||
|
||||
liquibase.update(Contexts())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import javax.persistence.MappedSuperclass
|
|||
@MappedSuperclass
|
||||
abstract class BaseEntity : Serializable {
|
||||
|
||||
@CreatedDate
|
||||
@CreatedDate(updatable = false)
|
||||
lateinit var createdAt: ZonedDateTime
|
||||
|
||||
@LastModifiedDate
|
||||
|
|
|
|||
|
|
@ -15,5 +15,9 @@ abstract class BaseUuidIdEntity : BaseEntity() {
|
|||
@GeneratedValue(generator = "uuid")
|
||||
@GenericGenerator(name = "uuid", strategy = "uuid2")
|
||||
open var id: UUID? = null
|
||||
@Suppress("RedundantSetter")
|
||||
protected set(id) {
|
||||
field = id
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ class SecurityAuditorAware(
|
|||
|
||||
Optional.ofNullable(result)
|
||||
}
|
||||
.or { Optional.of(UNKNOWN_USER) }
|
||||
.or { Optional.of(API_USER) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val UNKNOWN_USER = "unknownUser"
|
||||
const val API_USER = "api"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:Suppress("unused")
|
||||
|
||||
package ru.touchin.common.spring.security.auditor
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
|
|
|
|||
|
|
@ -5,26 +5,21 @@ import org.springframework.context.annotation.Configuration
|
|||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import ru.touchin.common.spring.security.url.interceptors.UrlExpressionRegistryInterceptor
|
||||
import ru.touchin.common.spring.security.http.configurators.HttpSecurityConfigurator
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("ru.touchin.common.spring.security.url.interceptors")
|
||||
@ComponentScan(
|
||||
"ru.touchin.common.spring.security.url.interceptors",
|
||||
"ru.touchin.common.spring.security.http.configurators",
|
||||
)
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
class DefaultSecurityConfiguration(
|
||||
private val urlExpressionRegistryInterceptors: List<UrlExpressionRegistryInterceptor>,
|
||||
private val httpSecurityConfigurators: List<HttpSecurityConfigurator>,
|
||||
) : WebSecurityConfigurerAdapter() {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http
|
||||
.cors().disable()
|
||||
.csrf().disable()
|
||||
.httpBasic().disable()
|
||||
.authorizeRequests { urlExpressionRegistry ->
|
||||
urlExpressionRegistryInterceptors.forEach {
|
||||
it.invoke(urlExpressionRegistry)
|
||||
}
|
||||
|
||||
urlExpressionRegistry.anyRequest().authenticated()
|
||||
httpSecurityConfigurators.forEach {
|
||||
it.configure(http)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.touchin.common.spring.security.http.configurators
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
import ru.touchin.common.spring.security.url.interceptors.UrlExpressionRegistryInterceptor
|
||||
|
||||
@Order(Ordered.NORMAL)
|
||||
@Component
|
||||
class AuthorizeRequestsHttpSecurityConfigurator(
|
||||
private val urlExpressionRegistryInterceptors: List<UrlExpressionRegistryInterceptor>,
|
||||
) : HttpSecurityConfigurator {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http.authorizeRequests { urlExpressionRegistry ->
|
||||
urlExpressionRegistryInterceptors.forEach {
|
||||
it.invoke(urlExpressionRegistry)
|
||||
}
|
||||
|
||||
urlExpressionRegistry.anyRequest().authenticated()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.common.spring.security.http.configurators
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class BasicHttpSecurityConfigurator : HttpSecurityConfigurator {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
// TODO: user properties
|
||||
http.httpBasic().disable()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.common.spring.security.http.configurators
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class CorsHttpSecurityConfigurator : HttpSecurityConfigurator {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
// TODO: user properties
|
||||
http.cors().disable()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.common.spring.security.http.configurators
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class CsrfHttpSecurityConfigurator : HttpSecurityConfigurator {
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
// TODO: use properties
|
||||
http.csrf().disable()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.common.spring.security.http.configurators
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
|
||||
interface HttpSecurityConfigurator {
|
||||
|
||||
fun configure(http: HttpSecurity)
|
||||
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ interface ApiError {
|
|||
|
||||
companion object {
|
||||
const val SUCCESS_CODE = 0
|
||||
const val FAILURE_CODE = -1
|
||||
const val BAD_REQUEST_CODE = -1
|
||||
const val FAILURE_CODE = -2
|
||||
const val NOT_FOUND_CODE = -3
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.common.spring.annotations
|
||||
|
||||
import org.springframework.context.annotation.Import
|
||||
import ru.touchin.common.spring.processors.RequiredByBeanDefinitionPostProcessor
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS)
|
||||
@Import(RequiredByBeanDefinitionPostProcessor::class)
|
||||
annotation class RequiredBy(vararg val value: String)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package ru.touchin.common.spring.processors
|
||||
|
||||
import org.springframework.beans.BeansException
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.annotations.RequiredBy
|
||||
|
||||
@Component
|
||||
class RequiredByBeanDefinitionPostProcessor : BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
@Throws(BeansException::class)
|
||||
override fun postProcessBeanDefinitionRegistry(registry: BeanDefinitionRegistry) {
|
||||
for (beanName in registry.beanDefinitionNames) {
|
||||
val beanClassName = registry.getBeanDefinition(beanName).beanClassName?:continue
|
||||
|
||||
getDependantBeanNames(beanClassName).forEach { dependantBeanName ->
|
||||
registry.getBeanDefinition(dependantBeanName).setDependsOn(beanName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BeansException::class)
|
||||
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
private fun getDependantBeanNames(beanClassName: String): List<String> {
|
||||
val beanClass = Class.forName(beanClassName)
|
||||
var dependantBeans = emptyList<String>()
|
||||
|
||||
if (beanClass.isAnnotationPresent(RequiredBy::class.java)) {
|
||||
dependantBeans = beanClass.getAnnotation(RequiredBy::class.java).value.toList()
|
||||
}
|
||||
|
||||
return dependantBeans
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package ru.touchin.common.territories.enums
|
||||
|
||||
enum class TerritoryCode(val countryName: String) {
|
||||
|
||||
AF("Afghanistan"),
|
||||
AX("Åland Islands"),
|
||||
AL("Albania"),
|
||||
DZ("Algeria"),
|
||||
AS("American Samoa"),
|
||||
AD("Andorra"),
|
||||
AO("Angola"),
|
||||
AI("Anguilla"),
|
||||
AQ("Antarctica"),
|
||||
AG("Antigua and Barbuda"),
|
||||
AR("Argentina"),
|
||||
AM("Armenia"),
|
||||
AW("Aruba"),
|
||||
AU("Australia"),
|
||||
AT("Austria"),
|
||||
AZ("Azerbaijan"),
|
||||
BS("Bahamas"),
|
||||
BH("Bahrain"),
|
||||
BD("Bangladesh"),
|
||||
BB("Barbados"),
|
||||
BY("Belarus"),
|
||||
BE("Belgium"),
|
||||
BZ("Belize"),
|
||||
BJ("Benin"),
|
||||
BM("Bermuda"),
|
||||
BT("Bhutan"),
|
||||
BO("Bolivia (Plurinational State of)"),
|
||||
BQ("Bonaire, Sint Eustatius and Saba"),
|
||||
BA("Bosnia and Herzegovina"),
|
||||
BW("Botswana"),
|
||||
BV("Bouvet Island"),
|
||||
BR("Brazil"),
|
||||
IO("British Indian Ocean Territory"),
|
||||
BN("Brunei Darussalam"),
|
||||
BG("Bulgaria"),
|
||||
BF("Burkina Faso"),
|
||||
BI("Burundi"),
|
||||
CV("Cabo Verde"),
|
||||
KH("Cambodia"),
|
||||
CM("Cameroon"),
|
||||
CA("Canada"),
|
||||
KY("Cayman Islands"),
|
||||
CF("Central African Republic"),
|
||||
TD("Chad"),
|
||||
CL("Chile"),
|
||||
CN("China"),
|
||||
CX("Christmas Island"),
|
||||
CC("Cocos (Keeling) Islands"),
|
||||
CO("Colombia"),
|
||||
KM("Comoros"),
|
||||
CG("Congo"),
|
||||
CD("Congo, Democratic Republic of the"),
|
||||
CK("Cook Islands"),
|
||||
CR("Costa Rica"),
|
||||
CI("Côte d'Ivoire"),
|
||||
HR("Croatia"),
|
||||
CU("Cuba"),
|
||||
CW("Curaçao"),
|
||||
CY("Cyprus"),
|
||||
CZ("Czechia"),
|
||||
DK("Denmark"),
|
||||
DJ("Djibouti"),
|
||||
DM("Dominica"),
|
||||
DO("Dominican Republic"),
|
||||
EC("Ecuador"),
|
||||
EG("Egypt"),
|
||||
SV("El Salvador"),
|
||||
GQ("Equatorial Guinea"),
|
||||
ER("Eritrea"),
|
||||
EE("Estonia"),
|
||||
SZ("Eswatini"),
|
||||
ET("Ethiopia"),
|
||||
FK("Falkland Islands (Malvinas)"),
|
||||
FO("Faroe Islands"),
|
||||
FJ("Fiji"),
|
||||
FI("Finland"),
|
||||
FR("France"),
|
||||
GF("French Guiana"),
|
||||
PF("French Polynesia"),
|
||||
TF("French Southern Territories"),
|
||||
GA("Gabon"),
|
||||
GM("Gambia"),
|
||||
GE("Georgia"),
|
||||
DE("Germany"),
|
||||
GH("Ghana"),
|
||||
GI("Gibraltar"),
|
||||
GR("Greece"),
|
||||
GL("Greenland"),
|
||||
GD("Grenada"),
|
||||
GP("Guadeloupe"),
|
||||
GU("Guam"),
|
||||
GT("Guatemala"),
|
||||
GG("Guernsey"),
|
||||
GN("Guinea"),
|
||||
GW("Guinea-Bissau"),
|
||||
GY("Guyana"),
|
||||
HT("Haiti"),
|
||||
HM("Heard Island and McDonald Islands"),
|
||||
VA("Holy See"),
|
||||
HN("Honduras"),
|
||||
HK("Hong Kong"),
|
||||
HU("Hungary"),
|
||||
IS("Iceland"),
|
||||
IN("India"),
|
||||
ID("Indonesia"),
|
||||
IR("Iran (Islamic Republic of)"),
|
||||
IQ("Iraq"),
|
||||
IE("Ireland"),
|
||||
IM("Isle of Man"),
|
||||
IL("Israel"),
|
||||
IT("Italy"),
|
||||
JM("Jamaica"),
|
||||
JP("Japan"),
|
||||
JE("Jersey"),
|
||||
JO("Jordan"),
|
||||
KZ("Kazakhstan"),
|
||||
KE("Kenya"),
|
||||
KI("Kiribati"),
|
||||
KP("Korea (Democratic People's Republic of)"),
|
||||
KR("Korea, Republic of"),
|
||||
KW("Kuwait"),
|
||||
KG("Kyrgyzstan"),
|
||||
LA("Lao People's Democratic Republic"),
|
||||
LV("Latvia"),
|
||||
LB("Lebanon"),
|
||||
LS("Lesotho"),
|
||||
LR("Liberia"),
|
||||
LY("Libya"),
|
||||
LI("Liechtenstein"),
|
||||
LT("Lithuania"),
|
||||
LU("Luxembourg"),
|
||||
MO("Macao"),
|
||||
MG("Madagascar"),
|
||||
MW("Malawi"),
|
||||
MY("Malaysia"),
|
||||
MV("Maldives"),
|
||||
ML("Mali"),
|
||||
MT("Malta"),
|
||||
MH("Marshall Islands"),
|
||||
MQ("Martinique"),
|
||||
MR("Mauritania"),
|
||||
MU("Mauritius"),
|
||||
YT("Mayotte"),
|
||||
MX("Mexico"),
|
||||
FM("Micronesia (Federated States of)"),
|
||||
MD("Moldova, Republic of"),
|
||||
MC("Monaco"),
|
||||
MN("Mongolia"),
|
||||
ME("Montenegro"),
|
||||
MS("Montserrat"),
|
||||
MA("Morocco"),
|
||||
MZ("Mozambique"),
|
||||
MM("Myanmar"),
|
||||
NA("Namibia"),
|
||||
NR("Nauru"),
|
||||
NP("Nepal"),
|
||||
NL("Netherlands"),
|
||||
NC("New Caledonia"),
|
||||
NZ("New Zealand"),
|
||||
NI("Nicaragua"),
|
||||
NE("Niger"),
|
||||
NG("Nigeria"),
|
||||
NU("Niue"),
|
||||
NF("Norfolk Island"),
|
||||
MK("North Macedonia"),
|
||||
MP("Northern Mariana Islands"),
|
||||
NO("Norway"),
|
||||
OM("Oman"),
|
||||
PK("Pakistan"),
|
||||
PW("Palau"),
|
||||
PS("Palestine, State of"),
|
||||
PA("Panama"),
|
||||
PG("Papua New Guinea"),
|
||||
PY("Paraguay"),
|
||||
PE("Peru"),
|
||||
PH("Philippines"),
|
||||
PN("Pitcairn"),
|
||||
PL("Poland"),
|
||||
PT("Portugal"),
|
||||
PR("Puerto Rico"),
|
||||
QA("Qatar"),
|
||||
RE("Réunion"),
|
||||
RO("Romania"),
|
||||
RU("Russian Federation"),
|
||||
RW("Rwanda"),
|
||||
BL("Saint Barthélemy"),
|
||||
SH("Saint Helena, Ascension and Tristan da Cunha"),
|
||||
KN("Saint Kitts and Nevis"),
|
||||
LC("Saint Lucia"),
|
||||
MF("Saint Martin (French part)"),
|
||||
PM("Saint Pierre and Miquelon"),
|
||||
VC("Saint Vincent and the Grenadines"),
|
||||
WS("Samoa"),
|
||||
SM("San Marino"),
|
||||
ST("Sao Tome and Principe"),
|
||||
SA("Saudi Arabia"),
|
||||
SN("Senegal"),
|
||||
RS("Serbia"),
|
||||
SC("Seychelles"),
|
||||
SL("Sierra Leone"),
|
||||
SG("Singapore"),
|
||||
SX("Sint Maarten (Dutch part)"),
|
||||
SK("Slovakia"),
|
||||
SI("Slovenia"),
|
||||
SB("Solomon Islands"),
|
||||
SO("Somalia"),
|
||||
ZA("South Africa"),
|
||||
GS("South Georgia and the South Sandwich Islands"),
|
||||
SS("South Sudan"),
|
||||
ES("Spain"),
|
||||
LK("Sri Lanka"),
|
||||
SD("Sudan"),
|
||||
SR("Suriname"),
|
||||
SJ("Svalbard and Jan Mayen"),
|
||||
SE("Sweden"),
|
||||
CH("Switzerland"),
|
||||
SY("Syrian Arab Republic"),
|
||||
TW("Taiwan, Province of China"),
|
||||
TJ("Tajikistan"),
|
||||
TZ("Tanzania, United Republic of"),
|
||||
TH("Thailand"),
|
||||
TL("Timor-Leste"),
|
||||
TG("Togo"),
|
||||
TK("Tokelau"),
|
||||
TO("Tonga"),
|
||||
TT("Trinidad and Tobago"),
|
||||
TN("Tunisia"),
|
||||
TR("Turkey"),
|
||||
TM("Turkmenistan"),
|
||||
TC("Turks and Caicos Islands"),
|
||||
TV("Tuvalu"),
|
||||
UG("Uganda"),
|
||||
UA("Ukraine"),
|
||||
AE("United Arab Emirates"),
|
||||
GB("United Kingdom of Great Britain and Northern Ireland"),
|
||||
US("United States of America"),
|
||||
UM("United States Minor Outlying Islands"),
|
||||
UY("Uruguay"),
|
||||
UZ("Uzbekistan"),
|
||||
VU("Vanuatu"),
|
||||
VE("Venezuela (Bolivarian Republic of)"),
|
||||
VN("Viet Nam"),
|
||||
VG("Virgin Islands (British)"),
|
||||
VI("Virgin Islands (U.S.)"),
|
||||
WF("Wallis and Futuna"),
|
||||
EH("Western Sahara"),
|
||||
YE("Yemen"),
|
||||
ZM("Zambia"),
|
||||
ZW("Zimbabwe"),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.common.byte
|
||||
|
||||
object ByteUtils {
|
||||
|
||||
fun ByteArray.toHex(): String {
|
||||
return joinToString(separator = "") { byte ->
|
||||
"%02x".format(byte)
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeHex(hex: String): ByteArray {
|
||||
check(hex.length % 2 == 0) { "Must have an even length" }
|
||||
|
||||
return hex.chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import java.time.LocalDate
|
|||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.TemporalAmount
|
||||
import java.util.*
|
||||
|
||||
object DateUtils {
|
||||
|
|
@ -20,9 +21,9 @@ object DateUtils {
|
|||
currentDate: ZonedDateTime = ZonedDateTime.now()
|
||||
) = !isExpired(currentDate)
|
||||
|
||||
fun ZonedDateTime.isExpired(duration: Duration) = isExpired(ZonedDateTime.now().minus(duration))
|
||||
fun ZonedDateTime.isExpired(temporalAmount: TemporalAmount) = isExpired(ZonedDateTime.now().minus(temporalAmount))
|
||||
|
||||
fun ZonedDateTime.isNotExpired(duration: Duration) = !isExpired(duration)
|
||||
fun ZonedDateTime.isNotExpired(temporalAmount: TemporalAmount) = !isExpired(temporalAmount)
|
||||
|
||||
fun ZonedDateTime.equals(arg: ZonedDateTime, maxDiff: Duration) =
|
||||
Duration.between(this, arg) <= maxDiff
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.common.page
|
||||
|
||||
interface GetPage {
|
||||
|
||||
val page: Int
|
||||
val size: Int
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.common.page
|
||||
|
||||
interface PageInfo {
|
||||
|
||||
val pageCount: Int
|
||||
val totalItemCount: Long
|
||||
|
||||
}
|
||||
|
|
@ -24,4 +24,6 @@ object SecureRandomStringGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
fun generateRandomBytes(size: Int) = ByteArray(size).also(secureRandom::nextBytes)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.common.security.hash
|
||||
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.security.MessageDigest
|
||||
|
||||
object HashUtils {
|
||||
|
||||
enum class HashAlgorithm(val code: String) {
|
||||
MD5("MD5"),
|
||||
SHA1("SHA-1"),
|
||||
SHA256("SHA-256"),
|
||||
}
|
||||
|
||||
fun String.calculateHash(algorithmName: HashAlgorithm): ByteArray {
|
||||
return MessageDigest.getInstance(algorithmName.code)
|
||||
.digest(this.toByteArray(UTF_8))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,12 +19,12 @@ object StringUtils {
|
|||
}
|
||||
|
||||
nextUpperCase -> {
|
||||
this.append(char.toUpperCase())
|
||||
this.append(char.uppercase())
|
||||
nextUpperCase = false
|
||||
}
|
||||
|
||||
!nextUpperCase -> {
|
||||
this.append(char.toLowerCase())
|
||||
else -> {
|
||||
this.append(char.lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,8 @@ object StringUtils {
|
|||
fun String.removeNonPrintableCharacters(): String {
|
||||
return this
|
||||
.transliterateCyrillic()
|
||||
.replace("[\\p{Cntrl}&&[^\r\n\t]]".toRegex(), "")// erases all the ASCII control characters
|
||||
.replace("\\p{C}".toRegex(), "")// removes non-printable characters from Unicode
|
||||
.replace("\\p{Cntrl}&&[^\r\n\t]".toRegex(), "") // erases all the ASCII control characters
|
||||
.replace("\\p{C}".toRegex(), "") // removes non-printable characters from Unicode
|
||||
.trim()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,402 @@
|
|||
build:
|
||||
maxIssues: 0
|
||||
weights:
|
||||
# complexity: 2
|
||||
# LongParameterList: 1
|
||||
# style: 1
|
||||
# comments: 1
|
||||
|
||||
processors:
|
||||
active: true
|
||||
exclude:
|
||||
# - 'FunctionCountProcessor'
|
||||
# - 'PropertyCountProcessor'
|
||||
# - 'ClassCountProcessor'
|
||||
# - 'PackageCountProcessor'
|
||||
# - 'KtFileCountProcessor'
|
||||
|
||||
formatting:
|
||||
active: true
|
||||
android: true
|
||||
autoCorrect: true
|
||||
MaximumLineLength:
|
||||
active: true
|
||||
|
||||
console-reports:
|
||||
active: false
|
||||
exclude:
|
||||
# - 'ProjectStatisticsReport'
|
||||
# - 'ComplexityReport'
|
||||
# - 'NotificationReport'
|
||||
# - 'FindingsReport'
|
||||
# - 'BuildFailureReport'
|
||||
|
||||
comments:
|
||||
active: false
|
||||
CommentOverPrivateFunction:
|
||||
active: false
|
||||
CommentOverPrivateProperty:
|
||||
active: false
|
||||
EndOfSentenceFormat:
|
||||
active: false
|
||||
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$)
|
||||
UndocumentedPublicClass:
|
||||
active: false
|
||||
searchInNestedClass: true
|
||||
searchInInnerClass: true
|
||||
searchInInnerObject: true
|
||||
searchInInnerInterface: true
|
||||
UndocumentedPublicFunction:
|
||||
active: false
|
||||
|
||||
complexity:
|
||||
active: true
|
||||
ComplexCondition:
|
||||
active: true
|
||||
threshold: 5
|
||||
ComplexInterface:
|
||||
active: false
|
||||
threshold: 10
|
||||
includeStaticDeclarations: false
|
||||
ComplexMethod:
|
||||
active: true
|
||||
threshold: 10
|
||||
ignoreSingleWhenExpression: true
|
||||
ignoreSimpleWhenEntries: true
|
||||
LabeledExpression:
|
||||
active: true
|
||||
LargeClass:
|
||||
active: true
|
||||
threshold: 800
|
||||
LongMethod:
|
||||
active: true
|
||||
threshold: 40
|
||||
LongParameterList:
|
||||
active: true
|
||||
functionThreshold: 10
|
||||
ignoreDefaultParameters: false
|
||||
MethodOverloading:
|
||||
active: false
|
||||
threshold: 5
|
||||
NamedArguments:
|
||||
active: false
|
||||
threshold: 2
|
||||
NestedBlockDepth:
|
||||
active: true
|
||||
threshold: 4
|
||||
StringLiteralDuplication:
|
||||
active: true
|
||||
threshold: 3
|
||||
ignoreAnnotation: true
|
||||
excludeStringsWithLessThan5Characters: true
|
||||
ignoreStringsRegex: '$^'
|
||||
TooManyFunctions:
|
||||
active: true
|
||||
thresholdInFiles: 20
|
||||
thresholdInClasses: 20
|
||||
thresholdInInterfaces: 20
|
||||
thresholdInObjects: 20
|
||||
thresholdInEnums: 20
|
||||
ignoreDeprecated: false
|
||||
ignorePrivate: false
|
||||
|
||||
empty-blocks:
|
||||
active: true
|
||||
EmptyCatchBlock:
|
||||
active: true
|
||||
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
|
||||
EmptyClassBlock:
|
||||
active: true
|
||||
EmptyDefaultConstructor:
|
||||
active: false
|
||||
EmptyDoWhileBlock:
|
||||
active: true
|
||||
EmptyElseBlock:
|
||||
active: true
|
||||
EmptyFinallyBlock:
|
||||
active: true
|
||||
EmptyForBlock:
|
||||
active: true
|
||||
EmptyFunctionBlock:
|
||||
active: true
|
||||
ignoreOverridden: false
|
||||
EmptyIfBlock:
|
||||
active: true
|
||||
EmptyInitBlock:
|
||||
active: true
|
||||
EmptyKtFile:
|
||||
active: true
|
||||
EmptySecondaryConstructor:
|
||||
active: true
|
||||
EmptyWhenBlock:
|
||||
active: true
|
||||
EmptyWhileBlock:
|
||||
active: true
|
||||
|
||||
exceptions:
|
||||
active: true
|
||||
ExceptionRaisedInUnexpectedLocation:
|
||||
active: true
|
||||
methodNames: 'toString,hashCode,equals,finalize'
|
||||
InstanceOfCheckForException:
|
||||
active: false
|
||||
NotImplementedDeclaration:
|
||||
active: true
|
||||
PrintStackTrace:
|
||||
active: true
|
||||
RethrowCaughtException:
|
||||
active: false
|
||||
ReturnFromFinally:
|
||||
active: false
|
||||
SwallowedException:
|
||||
active: false
|
||||
ThrowingExceptionFromFinally:
|
||||
active: false
|
||||
ThrowingExceptionInMain:
|
||||
active: false
|
||||
ThrowingExceptionsWithoutMessageOrCause:
|
||||
active: true
|
||||
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
|
||||
ThrowingNewInstanceOfSameException:
|
||||
active: false
|
||||
TooGenericExceptionCaught:
|
||||
active: false
|
||||
exceptionNames:
|
||||
- ArrayIndexOutOfBoundsException
|
||||
- Error
|
||||
- Exception
|
||||
- IllegalMonitorStateException
|
||||
- NullPointerException
|
||||
- IndexOutOfBoundsException
|
||||
- RuntimeException
|
||||
- Throwable
|
||||
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
|
||||
TooGenericExceptionThrown:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- Error
|
||||
- Exception
|
||||
- Throwable
|
||||
- RuntimeException
|
||||
|
||||
naming:
|
||||
active: true
|
||||
ClassNaming:
|
||||
active: true
|
||||
classPattern: '[A-Z$][a-zA-Z0-9$]*'
|
||||
ConstructorParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
privateParameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
EnumNaming:
|
||||
active: true
|
||||
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
|
||||
ForbiddenClassName:
|
||||
active: false
|
||||
forbiddenName: ''
|
||||
FunctionMaxLength:
|
||||
active: false
|
||||
maximumFunctionNameLength: 30
|
||||
FunctionMinLength:
|
||||
active: false
|
||||
minimumFunctionNameLength: 3
|
||||
FunctionNaming:
|
||||
active: true
|
||||
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
FunctionParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
MatchingDeclarationName:
|
||||
active: true
|
||||
MemberNameEqualsClassName:
|
||||
active: false
|
||||
ignoreOverridden: true
|
||||
ObjectPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
PackageNaming:
|
||||
active: false
|
||||
packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$'
|
||||
TopLevelPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Z][_A-Z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
VariableMaxLength:
|
||||
active: false
|
||||
maximumVariableNameLength: 64
|
||||
VariableMinLength:
|
||||
active: false
|
||||
minimumVariableNameLength: 1
|
||||
VariableNaming:
|
||||
active: true
|
||||
variablePattern: '[a-z][A-Za-z0-9]*'
|
||||
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
|
||||
performance:
|
||||
active: true
|
||||
ArrayPrimitive:
|
||||
active: false
|
||||
ForEachOnRange:
|
||||
active: true
|
||||
SpreadOperator:
|
||||
active: false
|
||||
UnnecessaryTemporaryInstantiation:
|
||||
active: true
|
||||
|
||||
potential-bugs:
|
||||
active: true
|
||||
DuplicateCaseInWhenExpression:
|
||||
active: true
|
||||
EqualsAlwaysReturnsTrueOrFalse:
|
||||
active: false
|
||||
EqualsWithHashCodeExist:
|
||||
active: true
|
||||
ExplicitGarbageCollectionCall:
|
||||
active: true
|
||||
InvalidRange:
|
||||
active: true
|
||||
IteratorHasNextCallsNextMethod:
|
||||
active: false
|
||||
IteratorNotThrowingNoSuchElementException:
|
||||
active: false
|
||||
LateinitUsage:
|
||||
active: false
|
||||
excludeAnnotatedProperties: ""
|
||||
ignoreOnClassesPattern: ""
|
||||
UnconditionalJumpStatementInLoop:
|
||||
active: false
|
||||
UnreachableCode:
|
||||
active: true
|
||||
UnsafeCallOnNullableType:
|
||||
active: false
|
||||
UnsafeCast:
|
||||
active: false
|
||||
UselessPostfixExpression:
|
||||
active: false
|
||||
WrongEqualsTypeParameter:
|
||||
active: false
|
||||
|
||||
style:
|
||||
active: true
|
||||
CollapsibleIfStatements:
|
||||
active: false
|
||||
DataClassContainsFunctions:
|
||||
active: false
|
||||
conversionFunctionPrefix: 'to'
|
||||
DestructuringDeclarationWithTooManyEntries:
|
||||
active: true
|
||||
maxDestructuringEntries: 5
|
||||
EqualsNullCall:
|
||||
active: true
|
||||
ExplicitItLambdaParameter:
|
||||
active: true
|
||||
ExpressionBodySyntax:
|
||||
active: false
|
||||
includeLineWrapping: false
|
||||
ForbiddenComment:
|
||||
active: true
|
||||
values: 'STOPSHIP:'
|
||||
ForbiddenImport:
|
||||
active: false
|
||||
imports: ''
|
||||
ForbiddenVoid:
|
||||
active: false
|
||||
FunctionOnlyReturningConstant:
|
||||
active: false
|
||||
ignoreOverridableFunction: true
|
||||
excludedFunctions: 'describeContents'
|
||||
LoopWithTooManyJumpStatements:
|
||||
active: false
|
||||
maxJumpCount: 1
|
||||
MagicNumber:
|
||||
active: false
|
||||
ignoreNumbers: '-1,0,1,2'
|
||||
ignoreHashCodeFunction: true
|
||||
ignorePropertyDeclaration: false
|
||||
ignoreConstantDeclaration: true
|
||||
ignoreCompanionObjectPropertyDeclaration: true
|
||||
ignoreAnnotation: false
|
||||
ignoreNamedArgument: true
|
||||
ignoreEnums: false
|
||||
MandatoryBracesIfStatements:
|
||||
active: true
|
||||
MaxLineLength:
|
||||
active: true
|
||||
maxLineLength: 150
|
||||
excludePackageStatements: true
|
||||
excludeImportStatements: true
|
||||
excludeCommentStatements: false
|
||||
MayBeConst:
|
||||
active: true
|
||||
ModifierOrder:
|
||||
active: true
|
||||
NestedClassesVisibility:
|
||||
active: false
|
||||
NewLineAtEndOfFile:
|
||||
active: true
|
||||
NoTabs:
|
||||
active: true
|
||||
OptionalAbstractKeyword:
|
||||
active: true
|
||||
OptionalUnit:
|
||||
active: false
|
||||
OptionalWhenBraces:
|
||||
active: false
|
||||
PreferToOverPairSyntax:
|
||||
active: true
|
||||
ProtectedMemberInFinalClass:
|
||||
active: true
|
||||
RedundantVisibilityModifierRule:
|
||||
active: true
|
||||
ReturnCount:
|
||||
active: true
|
||||
max: 3
|
||||
excludedFunctions: "equals"
|
||||
SafeCast:
|
||||
active: true
|
||||
SerialVersionUIDInSerializableClass:
|
||||
active: false
|
||||
SpacingBetweenPackageAndImports:
|
||||
active: true
|
||||
ThrowsCount:
|
||||
active: true
|
||||
max: 3
|
||||
TrailingWhitespace:
|
||||
active: true
|
||||
UnnecessaryAbstractClass:
|
||||
active: false
|
||||
excludeAnnotatedClasses: "dagger.Module"
|
||||
UnnecessaryApply:
|
||||
active: true
|
||||
UnnecessaryInheritance:
|
||||
active: true
|
||||
UnnecessaryLet:
|
||||
active: true
|
||||
UnnecessaryParentheses:
|
||||
active: true
|
||||
UntilInsteadOfRangeTo:
|
||||
active: true
|
||||
UnusedImports:
|
||||
active: true
|
||||
UnusedPrivateClass:
|
||||
active: true
|
||||
UnusedPrivateMember:
|
||||
active: true
|
||||
allowedNames: "(_|ignored|expected|serialVersionUID)"
|
||||
UtilityClassWithPublicConstructor:
|
||||
active: false
|
||||
VarCouldBeVal:
|
||||
active: true
|
||||
WildcardImport:
|
||||
active: true
|
||||
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
#### ENABLED RULES ####
|
||||
|
||||
# Checks that there is no more than one statement per line
|
||||
- name: MORE_THAN_ONE_STATEMENT_PER_LINE
|
||||
enabled: true
|
||||
# Checks that line breaks follow code style guide: rule 3.6
|
||||
- name: WRONG_NEWLINES
|
||||
enabled: true
|
||||
configuration:
|
||||
# If the number of parameters on one line is more than this threshold, all parameters will be placed on separate lines.
|
||||
maxParametersInOneLine: 2
|
||||
# 3 by default.
|
||||
maxCallsInOneLine: 2
|
||||
# Checks that indentation is correct
|
||||
- name: WRONG_INDENTATION
|
||||
enabled: true
|
||||
configuration:
|
||||
# Is newline at the end of a file needed
|
||||
newlineAtEnd: true
|
||||
# If true: in parameter list when parameters are split by newline they are indented with two indentations instead of one
|
||||
extendedIndentOfParameters: false
|
||||
# If true: if first parameter in parameter list is on the same line as opening parenthesis, then other parameters can be aligned with it
|
||||
alignedParameters: true
|
||||
# If true, expression bodies which begin on a separate line are indented
|
||||
# using a continuation indent. The default is false.
|
||||
#
|
||||
# This flag is called CONTINUATION_INDENT_FOR_EXPRESSION_BODIES in IDEA and
|
||||
# ij_kotlin_continuation_indent_for_expression_bodies in .editorconfig.
|
||||
extendedIndentForExpressionBodies: false
|
||||
# If true: if expression is split by newline after operator like +/-/`*`, then the next line is indented with two indentations instead of one
|
||||
extendedIndentAfterOperators: true
|
||||
# If true: when dot qualified expression starts on a new line, this line will be indented with two indentations instead of one
|
||||
extendedIndentBeforeDot: false
|
||||
# The indentation size for each file
|
||||
indentationSize: 4
|
||||
# Checks that braces are used in if, else, when, for, do, and while statements. Exception: single line ternary operator statement
|
||||
- name: NO_BRACES_IN_CONDITIONALS_AND_LOOPS
|
||||
enabled: true
|
||||
# Checks that annotation is on a single line
|
||||
- name: ANNOTATION_NEW_LINE
|
||||
enabled: true
|
||||
# Checks that the long lambda has parameters
|
||||
- name: TOO_MANY_LINES_IN_LAMBDA
|
||||
enabled: true
|
||||
configuration:
|
||||
maxLambdaLength: 1 # max length of lambda without parameters
|
||||
# Checks that the line length is < lineLength parameter
|
||||
- name: LONG_LINE
|
||||
enabled: true
|
||||
configuration:
|
||||
lineLength: '120'
|
||||
# Checks trailing comma
|
||||
- name: TRAILING_COMMA
|
||||
enabled: true
|
||||
configuration:
|
||||
# VALUE_ARGUMENT
|
||||
valueArgument: true
|
||||
# VALUE_PARAMETER
|
||||
valueParameter: true
|
||||
# REFERENCE_EXPRESSION
|
||||
indices: true
|
||||
# WHEN_CONDITION_WITH_EXPRESSION
|
||||
whenConditions: true
|
||||
# STRING_TEMPLATE
|
||||
collectionLiteral: true
|
||||
# TYPE_PROJECTION
|
||||
typeArgument: true
|
||||
# TYPE_PARAMETER
|
||||
typeParameter: true
|
||||
# DESTRUCTURING_DECLARATION_ENTRY
|
||||
destructuringDeclaration: true
|
||||
|
||||
#### DISABLED DEFAULT RULES ####
|
||||
|
||||
# Checks that the Class/Enum/Interface name matches Pascal case
|
||||
- name: CLASS_NAME_INCORRECT
|
||||
enabled: false
|
||||
# all code blocks with MyAnnotation will be ignored and not checked
|
||||
ignoreAnnotated: [ MyAnnotation ]
|
||||
# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE
|
||||
- name: CONSTANT_UPPERCASE
|
||||
enabled: false
|
||||
# Checks that enum value is in upper SNAKE_CASE or in PascalCase depending on the config. UPPER_SNAKE_CASE is the default, but can be changed by 'enumStyle' config
|
||||
- name: ENUM_VALUE
|
||||
enabled: false
|
||||
configuration:
|
||||
# Two options: SNAKE_CASE (default), PascalCase
|
||||
enumStyle: SNAKE_CASE
|
||||
# Checks that class which extends any Exception class has Exception suffix
|
||||
- name: EXCEPTION_SUFFIX
|
||||
enabled: false
|
||||
# Checks that file name has extension
|
||||
- name: FILE_NAME_INCORRECT
|
||||
enabled: false
|
||||
# Checks that file name matches class name, if it is only one class in file
|
||||
- name: FILE_NAME_MATCH_CLASS
|
||||
enabled: false
|
||||
# Checks that functions/methods which return boolean have special prefix like "is/should/e.t.c"
|
||||
- name: FUNCTION_BOOLEAN_PREFIX
|
||||
enabled: false
|
||||
configuration:
|
||||
allowedPrefixes: "" # A list of functions that return boolean and are allowed to use. Input is in a form "foo, bar".
|
||||
# Checks that function/method name is in lowerCamelCase
|
||||
- name: FUNCTION_NAME_INCORRECT_CASE
|
||||
enabled: false
|
||||
# Checks that typealias name is in PascalCase
|
||||
- name: TYPEALIAS_NAME_INCORRECT_CASE
|
||||
enabled: false
|
||||
# Checks that generic name doesn't contain more than 1 letter (capital). It can be followed by numbers, example: T12, T
|
||||
- name: GENERIC_NAME
|
||||
enabled: false
|
||||
# Identifier length should be in range [2,64] except names that used in industry like {i, j} and 'e' for catching exceptions
|
||||
- name: IDENTIFIER_LENGTH
|
||||
enabled: false
|
||||
# Checks that the object matches PascalCase
|
||||
- name: OBJECT_NAME_INCORRECT
|
||||
enabled: false
|
||||
# Checks that package name is in correct (lower) case
|
||||
- name: PACKAGE_NAME_INCORRECT_CASE
|
||||
enabled: false
|
||||
# Checks that package name starts with the company's domain
|
||||
- name: PACKAGE_NAME_INCORRECT_PREFIX
|
||||
enabled: false
|
||||
# Checks that package name does not have incorrect symbols like underscore or non-ASCII letters/digits
|
||||
- name: PACKAGE_NAME_INCORRECT_SYMBOLS
|
||||
enabled: false
|
||||
# Checks that the path for a file matches with a package name
|
||||
- name: PACKAGE_NAME_INCORRECT_PATH
|
||||
enabled: false
|
||||
# Checks that package name is in the file
|
||||
- name: PACKAGE_NAME_MISSING
|
||||
enabled: false
|
||||
# Checks that variable does not have prefix (like mVariable or M_VARIABLE)
|
||||
- name: VARIABLE_HAS_PREFIX
|
||||
enabled: false
|
||||
# Checks that variable does not contain one single letter, only exceptions are fixed names that used in industry like {i, j}
|
||||
- name: VARIABLE_NAME_INCORRECT
|
||||
enabled: false
|
||||
# Checks that the name of variable is in lowerCamelCase and contains only ASCII letters
|
||||
- name: VARIABLE_NAME_INCORRECT_FORMAT
|
||||
enabled: false
|
||||
# Checks that functions have kdoc
|
||||
- name: MISSING_KDOC_ON_FUNCTION
|
||||
enabled: false
|
||||
# Checks that on file level internal or public class or function has missing KDoc
|
||||
- name: MISSING_KDOC_TOP_LEVEL
|
||||
enabled: false
|
||||
# Checks that accessible internal elements (protected, public, internal) in a class are documented
|
||||
- name: MISSING_KDOC_CLASS_ELEMENTS
|
||||
enabled: false
|
||||
# Checks that accessible method parameters are documented in KDoc
|
||||
- name: KDOC_WITHOUT_PARAM_TAG
|
||||
enabled: false
|
||||
# Checks that accessible method explicit return type is documented in KDoc
|
||||
- name: KDOC_WITHOUT_RETURN_TAG
|
||||
enabled: false
|
||||
# Checks that accessible method throw keyword is documented in KDoc
|
||||
- name: KDOC_WITHOUT_THROWS_TAG
|
||||
enabled: false
|
||||
# Checks that KDoc is not empty
|
||||
- name: KDOC_EMPTY_KDOC
|
||||
enabled: false
|
||||
# Checks that underscore is correctly used to split package naming
|
||||
- name: INCORRECT_PACKAGE_SEPARATOR
|
||||
enabled: false
|
||||
# Checks that code block doesn't contain kdoc comments
|
||||
- name: COMMENTED_BY_KDOC
|
||||
enabled: false
|
||||
# Checks that there is no @deprecated tag in kdoc
|
||||
- name: KDOC_NO_DEPRECATED_TAG
|
||||
enabled: false
|
||||
# Checks that there is no empty content in kdoc tags
|
||||
- name: KDOC_NO_EMPTY_TAGS
|
||||
enabled: false
|
||||
# Checks that there is only one space after kdoc tag
|
||||
- name: KDOC_WRONG_SPACES_AFTER_TAG
|
||||
enabled: false
|
||||
# Checks tags order in kDoc. `@param`, `@return`, `@throws`
|
||||
- name: KDOC_WRONG_TAGS_ORDER
|
||||
enabled: false
|
||||
# Checks that there is no newline of empty KDoc line (with leading asterisk) between `@param`, `@return`, `@throws` tags
|
||||
- name: KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS
|
||||
enabled: false
|
||||
# Checks that block of tags @param, @return, @throws is separated from previous part of KDoc by exactly one empty line
|
||||
- name: KDOC_NEWLINES_BEFORE_BASIC_TAGS
|
||||
enabled: false
|
||||
# Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after
|
||||
- name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS
|
||||
enabled: false
|
||||
# Checks that kdoc does not contain @author tag or date
|
||||
- name: KDOC_CONTAINS_DATE_OR_AUTHOR
|
||||
enabled: false
|
||||
configuration:
|
||||
versionRegex: \d+\.\d+\.\d+[-.\w\d]*
|
||||
# Checks that KDoc does not contain single line with words 'return', 'get' or 'set'
|
||||
- name: KDOC_TRIVIAL_KDOC_ON_FUNCTION
|
||||
enabled: false
|
||||
# Checks that there is newline after header KDoc
|
||||
- name: HEADER_WRONG_FORMAT
|
||||
enabled: false
|
||||
# Checks that file with zero or >1 classes has header KDoc
|
||||
- name: HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE
|
||||
enabled: false
|
||||
# Checks that copyright exists on top of file and is properly formatted (as a block comment)
|
||||
- name: HEADER_MISSING_OR_WRONG_COPYRIGHT
|
||||
enabled: false
|
||||
configuration:
|
||||
isCopyrightMandatory: false
|
||||
copyrightText: 'Copyright (c) Your Company Name Here. 2010-;@currYear;'
|
||||
# Checks that header kdoc is located before package directive
|
||||
- name: HEADER_NOT_BEFORE_PACKAGE
|
||||
enabled: false
|
||||
# Checks that file does not contain lines > maxSize
|
||||
- name: FILE_IS_TOO_LONG
|
||||
enabled: false
|
||||
configuration:
|
||||
# number of lines
|
||||
maxSize: '2000'
|
||||
# Checks that file does not contain commented out code
|
||||
- name: COMMENTED_OUT_CODE
|
||||
enabled: false
|
||||
# Checks that file does not contain only comments, imports and package directive
|
||||
- name: FILE_CONTAINS_ONLY_COMMENTS
|
||||
enabled: false
|
||||
# Orders imports alphabetically
|
||||
- name: FILE_UNORDERED_IMPORTS
|
||||
enabled: false
|
||||
configuration:
|
||||
# use logical imports grouping with sorting inside of a group
|
||||
useRecommendedImportsOrder: true
|
||||
# Checks that general order of code parts is right
|
||||
- name: FILE_INCORRECT_BLOCKS_ORDER
|
||||
enabled: false
|
||||
# Checks that there is exactly one line between code blocks
|
||||
- name: FILE_NO_BLANK_LINE_BETWEEN_BLOCKS
|
||||
enabled: false
|
||||
# Checks that there is no wildcard imports. Exception: allowedWildcards
|
||||
- name: FILE_WILDCARD_IMPORTS
|
||||
enabled: false
|
||||
configuration:
|
||||
allowedWildcards: "" # Allowed wildcards for imports (e.g. "import org.cqfn.diktat.*, import org.jetbrains.kotlin.*")
|
||||
useRecommendedImportsOrder: true
|
||||
# Checks unused imports
|
||||
- name: UNUSED_IMPORT
|
||||
enabled: false
|
||||
configuration:
|
||||
deleteUnusedImport: true
|
||||
# Checks that the declaration part of a class-like code structures (class/interface/etc.) is in the proper order
|
||||
- name: WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES
|
||||
enabled: false
|
||||
# Checks that properties with comments are separated by a blank line
|
||||
- name: BLANK_LINE_BETWEEN_PROPERTIES
|
||||
enabled: false
|
||||
# Checks top level order
|
||||
- name: TOP_LEVEL_ORDER
|
||||
enabled: false
|
||||
# Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style)
|
||||
- name: BRACES_BLOCK_STRUCTURE_ERROR
|
||||
enabled: false
|
||||
configuration:
|
||||
openBraceNewline: 'True'
|
||||
closeBraceNewline: 'True'
|
||||
# Checks that there is no empty blocks in a file.
|
||||
# If allowEmptyBlocks is true, checks that it follows correct style (have a newline)
|
||||
- name: EMPTY_BLOCK_STRUCTURE_ERROR
|
||||
enabled: false
|
||||
configuration:
|
||||
# Whether a newline after `{` is required in an empty block
|
||||
styleEmptyBlockWithNewline: 'True'
|
||||
allowEmptyBlocks: 'False'
|
||||
# Checks that semicolons are not used at the end of a line
|
||||
- name: REDUNDANT_SEMICOLON
|
||||
enabled: false
|
||||
# Checks that there are not too many consecutive spaces in line
|
||||
- name: TOO_MANY_CONSECUTIVE_SPACES
|
||||
enabled: false
|
||||
configuration:
|
||||
# Maximum allowed number of consecutive spaces (not counting indentation)
|
||||
maxSpaces: '1'
|
||||
# Whether formatting for enums should be kept without checking
|
||||
saveInitialFormattingForEnums: false
|
||||
# Inspection that checks if a long dot qualified expression is used in condition or as an argument
|
||||
- name: COMPLEX_EXPRESSION
|
||||
enabled: false
|
||||
# Checks that blank lines are used correctly.
|
||||
# For example: triggers when there are too many blank lines between function declaration
|
||||
- name: TOO_MANY_BLANK_LINES
|
||||
enabled: false
|
||||
# Checks that usage of horizontal spaces doesn't violate code style guide
|
||||
- name: WRONG_WHITESPACE
|
||||
enabled: false
|
||||
# Checks that backticks (``) are not used in the identifier name, except the case when it is test method (marked with @Test annotation)
|
||||
- name: BACKTICKS_PROHIBITED
|
||||
enabled: false
|
||||
# Checks that a single line concatenation of strings is not used
|
||||
- name: STRING_CONCATENATION
|
||||
enabled: false
|
||||
# Checks that each when statement have else in the end
|
||||
- name: WHEN_WITHOUT_ELSE
|
||||
enabled: false
|
||||
# Checks that enum structure is correct: enum entries should be separated by comma and line break and last entry should have semicolon in the end.
|
||||
- name: ENUMS_SEPARATED
|
||||
enabled: false
|
||||
# Checks that value on integer or float constant is not too big
|
||||
- name: LONG_NUMERICAL_VALUES_SEPARATED
|
||||
enabled: false
|
||||
configuration:
|
||||
# Maximum number of digits which are not split
|
||||
maxNumberLength: '5'
|
||||
# Maximum number of digits between separators
|
||||
maxBlockLength: '3'
|
||||
# Checks magic number
|
||||
- name: MAGIC_NUMBER
|
||||
enabled: false
|
||||
configuration:
|
||||
# Ignore numbers from test
|
||||
ignoreTest: "true"
|
||||
# Ignore numbers
|
||||
ignoreNumbers: "-1, 1, 0, 2, 0U, 1U, 2U, -1L, 0L, 1L, 2L, 0UL, 1UL, 2UL"
|
||||
# Is ignore override hashCode function
|
||||
ignoreHashCodeFunction: "true"
|
||||
# Is ignore property
|
||||
ignorePropertyDeclaration: "false"
|
||||
# Is ignore local variable
|
||||
ignoreLocalVariableDeclaration: "false"
|
||||
# Is ignore constant
|
||||
ignoreConstantDeclaration: "true"
|
||||
# Is ignore property in companion object
|
||||
ignoreCompanionObjectPropertyDeclaration: "true"
|
||||
# Is ignore numbers in enum
|
||||
ignoreEnums: "false"
|
||||
# Is ignore number in ranges
|
||||
ignoreRanges: "false"
|
||||
# Is ignore number in extension function
|
||||
ignoreExtensionFunctions: "false"
|
||||
# Checks that order of enum values or constant property inside companion is correct
|
||||
- name: WRONG_DECLARATIONS_ORDER
|
||||
enabled: false
|
||||
configuration:
|
||||
# Whether enum members should be sorted alphabetically
|
||||
sortEnum: true
|
||||
# Whether class properties should be sorted alphabetically
|
||||
sortProperty: true
|
||||
# Checks that multiple modifiers sequence is in the correct order
|
||||
- name: WRONG_MULTIPLE_MODIFIERS_ORDER
|
||||
enabled: false
|
||||
# Checks that identifier has appropriate name (See table of rule 1.2 part 6)
|
||||
- name: CONFUSING_IDENTIFIER_NAMING
|
||||
enabled: false
|
||||
# Checks year in the copyright
|
||||
- name: WRONG_COPYRIGHT_YEAR
|
||||
enabled: false
|
||||
# Inspection that checks if local variables are declared close to the first usage site
|
||||
- name: LOCAL_VARIABLE_EARLY_DECLARATION
|
||||
enabled: false
|
||||
# Try to avoid initialize val by null (e.g. val a: Int? = null -> val a: Int = 0)
|
||||
- name: NULLABLE_PROPERTY_TYPE
|
||||
enabled: false
|
||||
# Inspection that checks if there is a blank line before kDoc and none after
|
||||
- name: WRONG_NEWLINES_AROUND_KDOC
|
||||
enabled: false
|
||||
# Inspection that checks if there is no blank lines before first comment
|
||||
- name: FIRST_COMMENT_NO_BLANK_LINE
|
||||
enabled: false
|
||||
# Inspection that checks if there are blank lines between code and comment and between code start token and comment's text
|
||||
- name: COMMENT_WHITE_SPACE
|
||||
enabled: false
|
||||
configuration:
|
||||
maxSpacesBeforeComment: 2
|
||||
maxSpacesInComment: 1
|
||||
# Inspection that checks if all comment's are inside if-else code blocks. Exception is general if comment
|
||||
- name: IF_ELSE_COMMENTS
|
||||
enabled: false
|
||||
# Type aliases provide alternative names for existing types when type's reference text is longer 25 chars
|
||||
- name: TYPE_ALIAS
|
||||
enabled: false
|
||||
configuration:
|
||||
typeReferenceLength: '25' # max length of type reference
|
||||
# Checks if casting can be omitted
|
||||
- name: SMART_CAST_NEEDED
|
||||
enabled: false
|
||||
# Checks that variables of generic types have explicit type declaration
|
||||
- name: GENERIC_VARIABLE_WRONG_DECLARATION
|
||||
enabled: false
|
||||
# Inspection that checks if string template has redundant curly braces
|
||||
- name: STRING_TEMPLATE_CURLY_BRACES
|
||||
enabled: false
|
||||
# Variables with `val` modifier - are immutable (read-only). Usage of such variables instead of `var` variables increases
|
||||
# robustness and readability of code, because `var` variables can be reassigned several times in the business logic.
|
||||
# This rule prohibits usage of `var`s as local variables - the only exception is accumulators and counters
|
||||
- name: SAY_NO_TO_VAR
|
||||
enabled: false
|
||||
# Inspection that checks if string template has redundant quotes
|
||||
- name: STRING_TEMPLATE_QUOTES
|
||||
enabled: false
|
||||
# Check if there are redundant nested if-statements, which could be collapsed into a single one by concatenating their conditions
|
||||
- name: COLLAPSE_IF_STATEMENTS
|
||||
enabled: false
|
||||
configuration:
|
||||
startCollapseFromNestedLevel: 2
|
||||
# Checks that floating-point values are not used in arithmetic expressions
|
||||
- name: FLOAT_IN_ACCURATE_CALCULATIONS
|
||||
enabled: false
|
||||
# Checks that function length isn't too long
|
||||
- name: TOO_LONG_FUNCTION
|
||||
enabled: false
|
||||
configuration:
|
||||
maxFunctionLength: '30' # max length of function
|
||||
isIncludeHeader: 'false' # count function's header
|
||||
# Warns if there are nested functions
|
||||
- name: AVOID_NESTED_FUNCTIONS
|
||||
enabled: false
|
||||
# Checks that lambda inside function parameters is in the end
|
||||
- name: LAMBDA_IS_NOT_LAST_PARAMETER
|
||||
enabled: false
|
||||
# Checks that function doesn't contains too many parameters
|
||||
- name: TOO_MANY_PARAMETERS
|
||||
enabled: false
|
||||
configuration:
|
||||
maxParameterListSize: '5' # max parameters size
|
||||
# Checks that function doesn't have too many nested blocks
|
||||
- name: NESTED_BLOCK
|
||||
enabled: false
|
||||
configuration:
|
||||
maxNestedBlockQuantity: '4'
|
||||
# Checks that function use default values, instead overloading
|
||||
- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS
|
||||
enabled: false
|
||||
# Checks that using runBlocking inside async block code
|
||||
- name: RUN_BLOCKING_INSIDE_ASYNC
|
||||
enabled: false
|
||||
# Checks that property in constructor doesn't contain comment
|
||||
- name: KDOC_NO_CONSTRUCTOR_PROPERTY
|
||||
enabled: false
|
||||
# Checks that using unnecessary, custom label
|
||||
- name: CUSTOM_LABEL
|
||||
enabled: false
|
||||
# Check that lambda with inner lambda doesn't use implicit parameter
|
||||
- name: PARAMETER_NAME_IN_OUTER_LAMBDA
|
||||
enabled: false
|
||||
# Checks that property in KDoc present in class
|
||||
- name: KDOC_EXTRA_PROPERTY
|
||||
enabled: false
|
||||
# There's a property in KDoc which is already present
|
||||
- name: KDOC_DUPLICATE_PROPERTY
|
||||
enabled: false
|
||||
# Checks that KDoc in constructor has property tag but with comment inside constructor
|
||||
- name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
|
||||
enabled: false
|
||||
# if a class has single constructor, it should be converted to a primary constructor
|
||||
- name: SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY
|
||||
enabled: false
|
||||
# Checks if class can be made as data class
|
||||
- name: USE_DATA_CLASS
|
||||
enabled: false
|
||||
# Checks that never use the name of a variable in the custom getter or setter
|
||||
- name: WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR
|
||||
enabled: false
|
||||
# Checks that classes have only one init block
|
||||
- name: MULTIPLE_INIT_BLOCKS
|
||||
enabled: false
|
||||
# Checks that there are abstract functions in abstract class
|
||||
- name: CLASS_SHOULD_NOT_BE_ABSTRACT
|
||||
enabled: false
|
||||
# Checks if there are any trivial getters or setters
|
||||
- name: TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED
|
||||
enabled: false
|
||||
# Checks that no custom getters and setters are used for properties. It is a more wide rule than TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED
|
||||
# Kotlin compiler automatically generates `get` and `set` methods for properties and also lets the possibility to override it.
|
||||
# But in all cases it is very confusing when `get` and `set` are overridden for a developer who uses this particular class.
|
||||
# Developer expects to get the value of the property, but receives some unknown value and some extra side effect hidden by the custom getter/setter.
|
||||
# Use extra functions for it instead.
|
||||
- name: CUSTOM_GETTERS_SETTERS
|
||||
enabled: false
|
||||
# Checks if null-check was used explicitly (for example: if (a == null))
|
||||
# Try to avoid explicit null checks (explicit comparison with `null`)
|
||||
# Kotlin is declared as [Null-safe](https://kotlinlang.org/docs/reference/null-safety.html) language.
|
||||
# But Kotlin architects wanted Kotlin to be fully compatible with Java, that's why `null` keyword was also introduced in Kotlin.
|
||||
# There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c
|
||||
- name: AVOID_NULL_CHECKS
|
||||
enabled: false
|
||||
# Checks if class instantiation can be wrapped in `apply` for better readability
|
||||
- name: COMPACT_OBJECT_INITIALIZATION
|
||||
enabled: false
|
||||
# Checks explicit supertype qualification
|
||||
- name: USELESS_SUPERTYPE
|
||||
enabled: false
|
||||
# Checks if extension function with the same signature don't have related classes
|
||||
- name: EXTENSION_FUNCTION_SAME_SIGNATURE
|
||||
enabled: false
|
||||
# Checks if there is empty primary constructor
|
||||
- name: EMPTY_PRIMARY_CONSTRUCTOR
|
||||
enabled: false
|
||||
# In case of not using field keyword in property accessors,
|
||||
# there should be explicit backing property with the name of real property
|
||||
# Example: val table get() {if (_table == null) ...} -> table should have _table
|
||||
- name: NO_CORRESPONDING_PROPERTY
|
||||
enabled: false
|
||||
# Checks if there is class/object that can be replace with extension function
|
||||
- name: AVOID_USING_UTILITY_CLASS
|
||||
enabled: false
|
||||
# If there is stateless class it is preferred to use object
|
||||
- name: OBJECT_IS_PREFERRED
|
||||
enabled: false
|
||||
# If there exists negated version of function you should prefer it instead of !functionCall
|
||||
- name: INVERSE_FUNCTION_PREFERRED
|
||||
enabled: false
|
||||
# Checks if class can be converted to inline class
|
||||
- name: INLINE_CLASS_CAN_BE_USED
|
||||
enabled: false
|
||||
# If file contains class, then it can't contain extension functions for the same class
|
||||
- name: EXTENSION_FUNCTION_WITH_CLASS
|
||||
enabled: false
|
||||
# Check if kts script contains other functions except run code
|
||||
- name: RUN_IN_SCRIPT
|
||||
enabled: false
|
||||
# Check if boolean expression can be simplified
|
||||
- name: COMPLEX_BOOLEAN_EXPRESSION
|
||||
enabled: false
|
||||
# Check if range can replace with until or `rangeTo` function with range
|
||||
- name: CONVENTIONAL_RANGE
|
||||
enabled: false
|
||||
configuration:
|
||||
isRangeToIgnore: false
|
||||
# Check if there is a call of print()\println() or console.log(). Assumption that it's a debug print
|
||||
- name: DEBUG_PRINT
|
||||
enabled: false
|
||||
# Check that typealias name is in PascalCase
|
||||
- name: TYPEALIAS_NAME_INCORRECT_CASE
|
||||
enabled: false
|
||||
# Should change property length - 1 to property lastIndex
|
||||
- name: USE_LAST_INDEX
|
||||
enabled: false
|
||||
# Only properties from the primary constructor should be documented in a @property tag in class KDoc
|
||||
- name: KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER
|
||||
enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.exception.handler.spring
|
||||
|
||||
import org.springframework.context.annotation.Import
|
||||
import ru.touchin.exception.handler.spring.configurations.ExceptionHandlerLoggerConfiguration
|
||||
|
||||
@Import(value = [ExceptionHandlerLoggerConfiguration::class])
|
||||
annotation class EnableSpringExceptionHandlerLogger
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
package ru.touchin.exception.handler.spring.configurations
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":exception-handler-spring-web"))
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.exception.handler.spring.security
|
||||
|
||||
import org.springframework.context.annotation.Import
|
||||
import ru.touchin.exception.handler.spring.security.configurations.SecurityExceptionHandlerConfiguration
|
||||
|
||||
@Import(value = [SecurityExceptionHandlerConfiguration::class])
|
||||
annotation class EnableSpringSecurityExceptionHandler
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.exception.handler.spring.security.configurations
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
|
||||
@ComponentScan("ru.touchin.exception.handler.spring.security.resolvers")
|
||||
class SecurityExceptionHandlerConfiguration
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package ru.touchin.exception.handler.spring.security.resolvers
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
import ru.touchin.common.spring.web.dto.DefaultApiError
|
||||
import ru.touchin.exception.handler.dto.ExceptionResolverResult
|
||||
import ru.touchin.exception.handler.spring.resolvers.ExceptionResolver
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class AccessDeniedExceptionResolver : ExceptionResolver {
|
||||
|
||||
override fun invoke(exception: Exception): ExceptionResolverResult? {
|
||||
if (exception is AccessDeniedException) {
|
||||
return createAccessDeniedError(exception)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createAccessDeniedError(exception: Exception?): ExceptionResolverResult {
|
||||
return ExceptionResolverResult(
|
||||
apiError = DefaultApiError.createFailure(exception?.message),
|
||||
status = HttpStatus.FORBIDDEN,
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.exception.handler
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import ru.touchin.exception.handler.spring.EnableSpringExceptionHandler
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableSpringExceptionHandler
|
||||
class TestApplication
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package ru.touchin.exception.handler.spring.advices
|
||||
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/errors")
|
||||
class ErrorController {
|
||||
|
||||
|
||||
@GetMapping("/unauthorized")
|
||||
fun runtimeError(): String {
|
||||
return "ok"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ActiveProfiles("test")
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
internal class ExceptionHandlerAdviceMvcTest {
|
||||
|
||||
@Autowired
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@Test
|
||||
@DisplayName("Should be unauthorized")
|
||||
fun shouldGetUnauthorized() {
|
||||
mockMvc
|
||||
.perform(get("/api/errors/unauthorized"))
|
||||
.andExpect(status().isUnauthorized)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":common"))
|
||||
api(project(":common-spring"))
|
||||
api(project(":common-spring-web"))
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ data class ExceptionResolverResult(
|
|||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createInternalError(errorMessage: String?): ExceptionResolverResult {
|
||||
return ExceptionResolverResult(
|
||||
apiError = DefaultApiError.createFailure(errorMessage),
|
||||
|
|
@ -26,6 +27,29 @@ data class ExceptionResolverResult(
|
|||
exception = exception
|
||||
)
|
||||
}
|
||||
|
||||
fun createNotFoundError(exception: Exception?): ExceptionResolverResult {
|
||||
return ExceptionResolverResult(
|
||||
apiError = DefaultApiError(
|
||||
errorCode = ApiError.NOT_FOUND_CODE,
|
||||
errorMessage = exception?.message
|
||||
),
|
||||
status = HttpStatus.NOT_FOUND,
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
|
||||
fun createBadRequestError(exception: Exception?): ExceptionResolverResult {
|
||||
return ExceptionResolverResult(
|
||||
apiError = DefaultApiError(
|
||||
errorCode = ApiError.BAD_REQUEST_CODE,
|
||||
errorMessage = exception?.message
|
||||
),
|
||||
status = HttpStatus.BAD_REQUEST,
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package ru.touchin.exception.handler.spring.advices
|
||||
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
import ru.touchin.exception.handler.dto.ExceptionResolverResult
|
||||
import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator
|
||||
import ru.touchin.exception.handler.spring.logger.Logger
|
||||
import ru.touchin.exception.handler.spring.properties.ExceptionResolverProperties
|
||||
import ru.touchin.exception.handler.spring.resolvers.ExceptionResolver
|
||||
|
||||
@RestControllerAdvice
|
||||
|
|
@ -13,6 +15,7 @@ class ExceptionHandlerAdvice(
|
|||
exceptionResolversList: List<ExceptionResolver>,
|
||||
private val logger: Logger,
|
||||
private val exceptionResponseBodyCreator: ExceptionResponseBodyCreator,
|
||||
private val exceptionResolverProperties: ExceptionResolverProperties,
|
||||
) {
|
||||
|
||||
private val exceptionResolvers = exceptionResolversList.asSequence()
|
||||
|
|
@ -30,7 +33,16 @@ class ExceptionHandlerAdvice(
|
|||
|
||||
val body = exceptionResponseBodyCreator(result.apiError)
|
||||
|
||||
return ResponseEntity(body, result.status)
|
||||
val headers = if (exceptionResolverProperties.includeHeaders) {
|
||||
HttpHeaders().apply {
|
||||
set("X-Error-Code", result.apiError.errorCode.toString())
|
||||
set("X-Error-Message", result.apiError.errorMessage)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return ResponseEntity(body, headers, result.status)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package ru.touchin.exception.handler.spring.configurations
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
|
@ -14,6 +15,7 @@ import ru.touchin.exception.handler.spring.logger.Logger
|
|||
"ru.touchin.exception.handler.spring.advices",
|
||||
"ru.touchin.exception.handler.spring.resolvers",
|
||||
)
|
||||
@ConfigurationPropertiesScan("ru.touchin.exception.handler.spring")
|
||||
class ExceptionHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.touchin.exception.handler.spring.properties
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.ConstructorBinding
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "exception.resolver")
|
||||
data class ExceptionResolverProperties(
|
||||
val includeHeaders: Boolean = false,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package ru.touchin.exception.handler.spring.resolvers
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.bind.MissingRequestValueException
|
||||
import ru.touchin.common.spring.Ordered
|
||||
import ru.touchin.exception.handler.dto.ExceptionResolverResult
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class MissingRequestValueExceptionResolver : ExceptionResolver {
|
||||
|
||||
override fun invoke(exception: Exception): ExceptionResolverResult? {
|
||||
if (exception is MissingRequestValueException) {
|
||||
return ExceptionResolverResult.createBadRequestError(exception)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package ru.touchin.exception.handler.spring.resolvers
|
||||
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.exceptions.CommonNotFoundException
|
||||
import ru.touchin.common.spring.Ordered
|
||||
import ru.touchin.exception.handler.dto.ExceptionResolverResult
|
||||
|
||||
@Order(Ordered.LOW)
|
||||
@Component
|
||||
class NotFoundExceptionResolver : ExceptionResolver {
|
||||
|
||||
override fun invoke(exception: Exception): ExceptionResolverResult? {
|
||||
if (exception is CommonNotFoundException) {
|
||||
return ExceptionResolverResult.createNotFoundError(exception)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import com.nhaarman.mockitokotlin2.mock
|
|||
import com.nhaarman.mockitokotlin2.never
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.hamcrest.Matchers.nullValue
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -25,9 +24,9 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import ru.touchin.exception.handler.spring.EnableSpringExceptionHandler
|
||||
import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator
|
||||
import ru.touchin.exception.handler.spring.logger.Logger
|
||||
import ru.touchin.exception.handler.spring.properties.ExceptionResolverProperties
|
||||
import ru.touchin.exception.handler.spring.resolvers.FallbackExceptionResolver
|
||||
import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver1
|
||||
import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver2
|
||||
|
|
@ -67,14 +66,14 @@ internal class ExceptionHandlerAdviceMvcTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Должна вернуться ошибка InternalServerError с кодом -1")
|
||||
@DisplayName("Должна вернуться ошибка InternalServerError с кодом -2")
|
||||
fun shouldGetInternalServerError() {
|
||||
mockMvc
|
||||
.perform(get("/api/errors/runtime"))
|
||||
.andDo(print())
|
||||
.andExpect(status().isInternalServerError)
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
|
||||
.andExpect(jsonPath("$.errorCode", `is`(-1)))
|
||||
.andExpect(jsonPath("$.errorCode", `is`(-2)))
|
||||
.andExpect(jsonPath("$.errorMessage", `is`("my runtime error")))
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +102,8 @@ internal class ExceptionHandlerAdviceMvcTest {
|
|||
val exceptionHandlerAdvice = ExceptionHandlerAdvice(
|
||||
exceptionResolversList = resolvers,
|
||||
exceptionResponseBodyCreator = exceptionResponseBodyCreator,
|
||||
logger = logger
|
||||
logger = logger,
|
||||
exceptionResolverProperties = ExceptionResolverProperties()
|
||||
)
|
||||
|
||||
exceptionHandlerAdvice.handleException(IllegalStateException("error"))
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest
|
|||
import org.springframework.test.context.ActiveProfiles
|
||||
import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator
|
||||
import ru.touchin.exception.handler.spring.logger.Logger
|
||||
import ru.touchin.exception.handler.spring.properties.ExceptionResolverProperties
|
||||
import ru.touchin.exception.handler.spring.resolvers.FallbackExceptionResolver
|
||||
import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver1
|
||||
import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver2
|
||||
|
|
@ -44,6 +45,7 @@ internal class ExceptionHandlerAdviceTest {
|
|||
exceptionResolversList = resolvers,
|
||||
exceptionResponseBodyCreator = exceptionResponseBodyCreator,
|
||||
logger = logger,
|
||||
exceptionResolverProperties = ExceptionResolverProperties()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +79,7 @@ internal class ExceptionHandlerAdviceTest {
|
|||
exceptionResolversList = resolvers,
|
||||
exceptionResponseBodyCreator = exceptionResponseBodyCreator,
|
||||
logger = logger,
|
||||
exceptionResolverProperties = ExceptionResolverProperties()
|
||||
)
|
||||
|
||||
exceptionHandlerAdvice.handleException(RuntimeException("error"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
id("org.springframework.boot")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation(project(":common-territories"))
|
||||
implementation(project(":common-spring-jpa"))
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
}
|
||||
|
||||
tasks.bootJar {
|
||||
enabled = false
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.geoip.core.configurations
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
|
||||
@ConfigurationPropertiesScan("ru.touchin.geoip.core")
|
||||
@ComponentScan("ru.touchin.geoip.core")
|
||||
class GeoipCoreConfiguration
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ru.touchin.geoip.core.configurations
|
||||
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
import ru.touchin.common.spring.jpa.liquibase.LiquibaseParams
|
||||
|
||||
@ComponentScan("ru.touchin.common.spring.jpa.liquibase")
|
||||
@EntityScan("ru.touchin.geoip.core")
|
||||
@EnableJpaRepositories(value = ["ru.touchin.geoip.core"])
|
||||
@EnableTransactionManagement
|
||||
class GeoipCoreDatabaseConfiguration {
|
||||
|
||||
@Bean
|
||||
fun liquibaseParams(): LiquibaseParams {
|
||||
return LiquibaseParams(
|
||||
schema = SCHEMA,
|
||||
changeLogPath = "geoip/db/changelog/db.changelog-master.yaml",
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCHEMA: String = "geoip"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.geoip.core.exceptions
|
||||
|
||||
import ru.touchin.common.exceptions.CommonNotFoundException
|
||||
|
||||
class GeoipLookupFailedException(
|
||||
ipAddress: String
|
||||
) : CommonNotFoundException("Geoip lookup failed for ip address $ipAddress")
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package ru.touchin.geoip.core.models
|
||||
|
||||
import ru.touchin.common.territories.enums.TerritoryCode
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
import javax.persistence.Enumerated
|
||||
import javax.persistence.Id
|
||||
|
||||
@Entity
|
||||
class GeoipLookupEntity {
|
||||
|
||||
@Id
|
||||
var geonameId: Long? = null
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
lateinit var territoryCode: TerritoryCode
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ru.touchin.geoip.core.repositories
|
||||
|
||||
import ru.touchin.geoip.core.exceptions.GeoipLookupFailedException
|
||||
import ru.touchin.geoip.core.models.GeoipLookupEntity
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.CrudRepository
|
||||
import ru.touchin.geoip.core.configurations.GeoipCoreDatabaseConfiguration.Companion.SCHEMA
|
||||
|
||||
interface GeoipLookupRepository : CrudRepository<GeoipLookupEntity, Long> {
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT "location"."geoname_id" AS "geoname_id",
|
||||
"location"."country_iso_code" AS "territory_code"
|
||||
FROM "$SCHEMA"."geoip2_network" "network"
|
||||
JOIN "$SCHEMA"."geoip2_location" "location"
|
||||
ON "network"."geoname_id" = "location"."geoname_id"
|
||||
AND "location"."locale_code" = 'en'
|
||||
WHERE "network"."network" >> CAST(:ipAddress AS inet)
|
||||
""",
|
||||
nativeQuery = true,
|
||||
)
|
||||
fun findByIpAddressOrNull(ipAddress: String): GeoipLookupEntity?
|
||||
|
||||
}
|
||||
|
||||
fun GeoipLookupRepository.findByIpAddress(ipAddress: String): GeoipLookupEntity {
|
||||
return findByIpAddressOrNull(ipAddress)
|
||||
?: throw GeoipLookupFailedException(ipAddress)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.geoip.core.services
|
||||
|
||||
import ru.touchin.common.territories.enums.TerritoryCode
|
||||
|
||||
interface GeoipLookupService {
|
||||
|
||||
fun get(ipAddress: String): TerritoryCode
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ru.touchin.geoip.core.services
|
||||
|
||||
import ru.touchin.geoip.core.repositories.GeoipLookupRepository
|
||||
import ru.touchin.geoip.core.repositories.findByIpAddress
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import ru.touchin.common.territories.enums.TerritoryCode
|
||||
|
||||
@Service
|
||||
class GeoipLookupServiceImpl(
|
||||
private val territoryCodeByInetAddressRepository: GeoipLookupRepository,
|
||||
) : GeoipLookupService {
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
override fun get(ipAddress: String): TerritoryCode {
|
||||
return territoryCodeByInetAddressRepository.findByIpAddress(ipAddress)
|
||||
.territoryCode
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE "geoip2_network" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"network" CIDR NOT NULL,
|
||||
"geoname_id" INT,
|
||||
"registered_country_geoname_id" INT,
|
||||
"represented_country_geoname_id" INT,
|
||||
"is_anonymous_proxy" BOOL,
|
||||
"is_satellite_provider" BOOL
|
||||
);
|
||||
|
||||
CREATE INDEX ON "geoip2_network" USING "gist" ("network" "inet_ops");
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE "geoip2_location" (
|
||||
"geoname_id" INT NOT NULL,
|
||||
"locale_code" TEXT NOT NULL,
|
||||
"continent_code" TEXT NOT NULL,
|
||||
"continent_name" TEXT NOT NULL,
|
||||
"country_iso_code" TEXT,
|
||||
"country_name" TEXT,
|
||||
"is_in_european_union" BOOL NOT NULL,
|
||||
PRIMARY KEY ("geoname_id", "locale_code")
|
||||
);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
databaseChangeLog:
|
||||
- includeAll:
|
||||
path: classpath*:/geoip/db/changelog/core
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
kotlin.code.style=official
|
||||
springBootVersion=2.4.1
|
||||
springBootVersion=2.5.3
|
||||
springDependencyManagementVersion=1.0.11.RELEASE
|
||||
kotlinVersion=1.4.32
|
||||
detektGradlePluginVersion=1.18.0
|
||||
diktatGradlePluginVersion=1.2.5
|
||||
kotlinVersion=1.6.21
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.logger.spring.web.configurations
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
|
||||
@ComponentScan("ru.touchin.logger.spring.web")
|
||||
class SpringLoggerWebConfiguration
|
||||
|
|
@ -33,11 +33,15 @@ class LoggingInterceptor(
|
|||
val uri = request.requestURI.let(::URI)
|
||||
|
||||
LoggerExecutionContext.current.updateContext { context ->
|
||||
context.plus(DefaultContextFields.id.name to UUID.randomUUID().toString())
|
||||
context.plus(DefaultContextFields.host.name to uri.host)
|
||||
context.plus(DefaultContextFields.path.name to uri.path)
|
||||
context.plus(DefaultContextFields.httpMethod.name to request.method)
|
||||
context.plus(DefaultContextFields.ipAddress.name to request.publicIp)
|
||||
context.plus(
|
||||
mapOf(
|
||||
DefaultContextFields.id.name to UUID.randomUUID().toString(),
|
||||
DefaultContextFields.host.name to uri.host,
|
||||
DefaultContextFields.path.name to uri.path,
|
||||
DefaultContextFields.httpMethod.name to request.method,
|
||||
DefaultContextFields.ipAddress.name to request.publicIp,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
logBuilderFactory.create(this::class.java)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package ru.touchin.logger.spring.web.webclients
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
||||
import ru.touchin.logger.dto.LogData
|
||||
import ru.touchin.logger.factory.LogBuilderFactory
|
||||
|
||||
@Component
|
||||
class DefaultWebClientLogger(
|
||||
private val logBuilderFactory: LogBuilderFactory<LogData>
|
||||
) : WebClientLogger {
|
||||
|
||||
override fun log(requestLogData: RequestLogData) {
|
||||
logBuilderFactory.create(this::class.java)
|
||||
.addTags(*requestLogData.logTags.toTypedArray())
|
||||
.setMethod(requestLogData.method.toString())
|
||||
.addData("uri" to requestLogData.uri)
|
||||
.also { builder ->
|
||||
requestLogData.requestBody?.also {
|
||||
builder.addData(
|
||||
"requestBody" to it,
|
||||
)
|
||||
}
|
||||
|
||||
requestLogData.responseBody?.also {
|
||||
builder.addData(
|
||||
"responseData" to it,
|
||||
)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
.log()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ dependencies {
|
|||
|
||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation(project(":common-spring"))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.aspectj.lang.ProceedingJoinPoint
|
|||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.reflect.MethodSignature
|
||||
import org.slf4j.LoggerFactory
|
||||
import ru.touchin.logger.spring.annotations.AutoLogging
|
||||
import ru.touchin.logger.spring.annotations.LogValue
|
||||
import ru.touchin.logger.builder.LogDataItem
|
||||
|
|
@ -22,6 +23,7 @@ class LogAspect(
|
|||
) {
|
||||
|
||||
@Around("@annotation(autoLoggingAnnotation)")
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
fun logInvocation(pjp: ProceedingJoinPoint, autoLoggingAnnotation: AutoLogging): Any? {
|
||||
val duration = LogDuration()
|
||||
|
||||
|
|
@ -46,8 +48,11 @@ class LogAspect(
|
|||
.build()
|
||||
.error()
|
||||
} catch (logError: Throwable) {
|
||||
error.printStackTrace()
|
||||
logError.printStackTrace()
|
||||
LoggerFactory.getLogger(this::class.java)
|
||||
.let { logger ->
|
||||
logger.error("Cannot build logger", error)
|
||||
logger.error("Cannot create logger", logError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package ru.touchin.logger.spring.serializers.resolvers
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.common.spring.Ordered
|
||||
|
|
@ -13,6 +14,7 @@ import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
|
|||
class ObjectLogValueResolverImpl : LogValueResolver<String> {
|
||||
|
||||
private val objectMapper = ObjectMapper()
|
||||
.registerModule(JavaTimeModule())
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
|
||||
override operator fun invoke(value: Any): ResolvedValue<String> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package ru.touchin.logger.serializers.impl
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
|
@ -25,6 +26,7 @@ internal class LogValueFieldResolverImplTest {
|
|||
private lateinit var logValueFieldSerializer: LogValueFieldSerializer
|
||||
|
||||
private val objectMapper = ObjectMapper()
|
||||
.registerModule(JavaTimeModule())
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
|
||||
@Suppress("unused")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ dependencies {
|
|||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
|
||||
implementation("ch.qos.logback.contrib:logback-jackson")
|
||||
implementation("ch.qos.logback.contrib:logback-json-classic")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface LogBuilder<T> {
|
|||
fun addData(vararg items: LogDataItem): LogBuilder<T>
|
||||
fun setMethod(method: String): LogBuilder<T>
|
||||
fun setContext(context: LogExecutionContextData): LogBuilder<LogData>
|
||||
fun setContext(): LogBuilder<LogData>
|
||||
fun build(): Log<T>
|
||||
fun isEnabled(logLevel: LogLevel): Boolean
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,14 @@ class LogBuilderImpl(
|
|||
logData.method = method
|
||||
}
|
||||
|
||||
override fun setContext(context: LogExecutionContextData): LogBuilder<LogData> = also {
|
||||
override fun setContext(): LogBuilder<LogData> = also {
|
||||
logData.ctx = LoggerExecutionContext.current.get()
|
||||
}
|
||||
|
||||
override fun setContext(context: LogExecutionContextData): LogBuilder<LogData> = also {
|
||||
logData.ctx = context
|
||||
}
|
||||
|
||||
override fun build(): Log<LogData> {
|
||||
if (logData.ctx == null) {
|
||||
logData.ctx = LoggerExecutionContext.current.get()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package ru.touchin.logger.context
|
||||
|
||||
@Suppress("unused", "EnumEntryName")
|
||||
@Suppress("unused", "EnumEntryName", "EnumNaming")
|
||||
enum class DefaultContextFields {
|
||||
id,
|
||||
host,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import ru.touchin.logger.context.LogExecutionContextData
|
||||
|
||||
class LogData {
|
||||
|
||||
val tags = mutableSetOf<String>()
|
||||
|
||||
@JsonIgnore
|
||||
var error: Throwable? = null
|
||||
|
||||
var duration: Long? = null
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
enum class LogLevel {
|
||||
Trace, Info, Error
|
||||
Trace, Debug, Info, Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package ru.touchin.logger.log
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import org.slf4j.LoggerFactory
|
||||
import ru.touchin.logger.dto.LogLevel
|
||||
import ru.touchin.logger.dto.LogData
|
||||
|
|
@ -26,6 +31,14 @@ abstract class AbstractLog(clazz: Class<*>) : Log<LogData> {
|
|||
}
|
||||
}
|
||||
|
||||
override fun debug() {
|
||||
if (logger.isDebugEnabled) {
|
||||
val logMessage = getMessage()
|
||||
|
||||
logger.debug(logMessage.message, logMessage.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info() {
|
||||
if (logger.isInfoEnabled) {
|
||||
val logMessage = getMessage()
|
||||
|
|
@ -53,9 +66,20 @@ abstract class AbstractLog(clazz: Class<*>) : Log<LogData> {
|
|||
override fun isEnabled(level: LogLevel): Boolean {
|
||||
return when(level) {
|
||||
LogLevel.Trace -> logger.isTraceEnabled
|
||||
LogLevel.Debug -> logger.isDebugEnabled
|
||||
LogLevel.Info -> logger.isInfoEnabled
|
||||
LogLevel.Error -> logger.isErrorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun objectMapper() = objectMapper
|
||||
|
||||
companion object {
|
||||
val objectMapper: ObjectMapper = JsonMapper.builder()
|
||||
.addModule(JavaTimeModule())
|
||||
.build()
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue