Compare commits

...

107 Commits

Author SHA1 Message Date
Artem Tsebrov 5e0fce2a60 Update testcontainers versions for better docker compatibility (#2)
https://github.com/testcontainers/testcontainers-java/releases/tag/1.18.3
Co-authored-by: Artyom Tsebrov <artem.tsebrov@touchin.ru>
Reviewed-on: #2
2023-08-04 17:38:17 +03:00
Artem Tsebrov a070a94381 Codestyle archunit (#1)
Co-authored-by: Artyom Tsebrov <artem.tsebrov@touchin.ru>
Reviewed-on: #1
2023-04-18 17:17:14 +03:00
Alexander Buntakov 0976db1e47 Merge branch 'feature/kotlin-styleguide-lint' into 'master'
Kotlin Styleguide plugins

See merge request touchinstinct/Backend-common!1
2023-04-06 16:54:03 +00:00
Artem Tsebrov e17454b996 Kotlin Styleguide plugins 2023-04-06 16:54:03 +00:00
TonCherAmi c8ed908656
Mark createdAt as not updatable (#108) 2023-01-13 17:30:42 +03:00
Artyom 20f07a4a9d
Implement "debug" level for logging (#107) 2023-01-10 14:02:39 +03:00
Alexander Buntakov a85e655aba improve simple logger
improve simple logger
2023-01-05 16:48:42 +03:00
Alexander Buntakov 3c837cf1d1
Feature/telegram bot (#106)
* add telegram bot

* remove unused dependencies

* add hasCommand
2022-12-12 14:14:13 +03:00
Ilia Ravin 0fda706e9f
detekt support (#90)
* detekt support

Co-authored-by: Alexander Buntakov <alexander.buntakov@gmail.com>
2022-11-12 18:59:35 +03:00
Artyom 9281a5d213
Push message provider HPK DTO's builder fixes (#105)
* Use correct namings for args, variables and fix builder methods
2022-11-12 17:04:55 +03:00
Ilia Ravin 844c0a9c73
Feature/smart migration (#86)
* smart migrations module

Co-authored-by: Илья Равин <i.ravin@iac.spb.ru>
2022-11-12 16:11:58 +03:00
Artyom d2ddf042e6
Allow to set values only internally (#104)
Prevent users of BaseUuidEntity from "GeneratedValue" misuse which will override even "not-null" values
2022-11-11 15:43:28 +03:00
TonCherAmi 8da83cea86
Implement push message provider mock check method (#103) 2022-11-09 18:23:48 +03:00
Artyom 8e08f8a570
Push message provider for Huawei Push Kit (#102)
* Add basic changes for HPK push provider (#88)
* Add HPK Android-related DTO's (#89)
* Add IOS and Web related DTO's (#93)
* Add common and base DTO's (#94)
* Add HMS Oauth and HMS HPK WebClients and request DTO's (#95)
* Add services for HPS clients and implement push message provider service (#96)
* Cover services with tests (#97)
* Update Readme for new push message provider modules (#98)
* Use new PushMessageSendResult class (#99)
* Various fixes of bean/class naming and tests (#100)
* FIll missing points in readme (#101)
2022-11-08 15:42:25 +03:00
Artyom 7710442654
Fix push message provider fcm module tests and converter (#92)
* Fix test invokations

* Fix DateConverter
2022-11-07 16:23:29 +03:00
TonCherAmi 171cacf30d
Add push message provider mock (#91) 2022-11-03 18:19:14 +03:00
Artyom 723e5a4178
Map "senderIdMismatch" error as "invalidPushToken" exception in FCM provider (#85)
* Map "senderIdMismatch" error as "invalidPushToken" exception
2022-10-25 16:41:43 +03:00
Artyom faf1e92633
Allow background push notifications on iOS (#84)
* Allow background update for iOS devices

* Update tests
2022-10-13 13:45:24 +03:00
Artyom eef1504171
Add notification sound for iOS and Android (#83) 2022-10-12 13:47:22 +03:00
TonCherAmi 1d63ab6156
Add user agent parsing module (#82) 2022-09-26 23:34:20 +03:00
TonCherAmi 709c71e48e
Make push notification title, description nullable (#81)
* Make push notification title nullable

* Make push notification description nullable
2022-09-19 20:57:31 +03:00
TonCherAmi cd9e12f6d9
Make push notification imageUrl nullable (#80) 2022-08-22 19:46:53 +03:00
Artyom 628c992b8d
Add multiple options for FCM authorization (#79)
* Add extra authorization types

* Update readme file
2022-08-22 15:28:51 +03:00
Artyom 1a069b8b6f
Push message provider validation (#78)
* Add method to PushMessageProviderService interface to validate push tokens

* Implement method in PushMessageProviderFcmService
2022-08-16 15:52:34 +03:00
Artyom ac9cac3049
Push message provider modules (#77)
* Add push-message-provider module

* Add push-message-provider-fcm module

* Update readme

* Update PushMessageProviderServiceFactory implementation

* Create FcmClient class to make integration implementation closed
2022-08-12 16:59:59 +03:00
Denis Kazantsev 1c5ef179ef
Added property endpoint (#76) 2022-07-14 16:02:02 +03:00
TonCherAmi eeaa02e7ea
Remove geoip schema creation script (#75) 2022-05-26 13:41:20 +03:00
TonCherAmi 5109c001cc
Add geoip-core (#74)
* Add common-territories

* Add geoip-core

* Use schema name constant instead of hardcoding it
2022-05-25 17:56:18 +03:00
TonCherAmi ea86f97485
Add support for multiple instances of LiquibaseParams (#73) 2022-05-17 13:23:52 +03:00
Denis Kazantsev 68f97ca05a
Feature/add server info controller (#72) 2022-04-11 14:45:17 +03:00
Denis Kazantsev b668819aa2
Feature/add server info (#71) 2022-04-08 19:36:12 +03:00
Denis Kazantsev be1ef45fb2
Added httpHeaders in ExceptionHandlerAdvice (#70) 2022-03-30 14:40:16 +03:00
Denis Kazantsev 345db02feb
Added setPassword method, dto (#69) 2022-03-22 19:40:44 +03:00
Alexander Buntakov ae3b8b109e
Merge pull request #68 from TouchInstinct/feature/page-sort
Add sort to GatPage interface
2022-03-17 13:13:28 +03:00
Alexander Buntakov be1d99da35 Add sort to GatPage interface 2022-03-17 13:03:47 +03:00
TonCherAmi a144f06bb8
Add page interfaces (#67) 2022-02-18 19:54:07 +03:00
Mikhail Yasnov 0f83486593
Fix saving user scopes (#66) 2022-02-08 18:40:46 +03:00
Mikhail Yasnov 720ecb8788
Method to add scopes to user (#65) 2022-02-08 14:48:17 +03:00
Denis Kazantsev bc8d9cb20d
GetUrl interface service not null (#64) 2022-01-23 23:37:39 +03:00
Denis Kazantsev 40254bfd58
Added dependency, exception (#63) 2022-01-23 23:20:50 +03:00
Denis Kazantsev 9bf9ae2695
Added delete method to s3 service (#62) 2022-01-20 18:28:11 +03:00
Artyom 144671a392
Обновление методов сервиса FileStorage (#61)
* Add "getUrl" and "uploadBytes" methods to file storage

* Add "contentType" param for file storage requests

* Create common interface for DTO and merge "upload" methods
2022-01-17 20:18:06 +03:00
Денис Казанцев 62664f893a
Changed datatype in dateUtils (#60) 2022-01-12 19:07:36 +03:00
Alexander Buntakov 8be6b184aa
add jpa modelgen 2022-01-11 13:40:31 +03:00
Alexander Buntakov 48ab57e96d add jpa modelgen 2022-01-10 21:34:44 +03:00
TonCherAmi d55d9bd600
Do not instantiate SecureRandom on every call (#58) 2021-12-17 11:22:51 +03:00
Mikhail Yasnov 735a71c9cd
S3 storage services (#56) 2021-11-08 17:13:53 +03:00
Alexander Buntakov d176e846b7
add missing value resolver (#55) 2021-11-03 15:57:30 +03:00
Alexander Buntakov 423d4213f5
fix email regexp (#54) 2021-10-23 00:19:02 +03:00
Alexander Buntakov a3d92210b9
Add DateTime serializer (#53) 2021-10-09 14:41:21 +03:00
TonCherAmi ec5d050c5d
Add common-messaging (#51) 2021-08-26 15:25:00 +03:00
TonCherAmi 5472e9dfdf
Miscellaneous changes (#50)
* Add captcha feature toggle

* Rename constant

* Up spring + kotlin versions

* Add spring security exception handler module

* Fix broken object mappers

* Add default spring web logger

* Add captcha configuration

* Enable captcha aspect by default
2021-08-19 20:07:41 +03:00
TonCherAmi dfa3a75f4c
Add captcha module (#49)
* Add captcha module

* Rename siteverify -> siteVerify
2021-08-18 17:47:33 +03:00
TonCherAmi 7313b43dff
Document auth modules (#48) 2021-08-18 17:43:54 +03:00
TonCherAmi 2a3561135b
More auth refactoring (#47) 2021-08-18 13:22:19 +03:00
TonCherAmi b1780329ea
Refactor auth modules (#46) 2021-08-17 10:47:00 +03:00
TonCherAmi 3898a48095
Add oauth2 metadata endpoints (#45)
* Add oauth2 metadata endpoints

* Rename metadata -> oauth2Metadata
2021-08-12 17:41:17 +03:00
Alexander Buntakov 7c73d2418c
add secure methods (#44) 2021-08-11 20:12:37 +03:00
TonCherAmi 195d2f16c9
Add missing jwt claim exception (#43) 2021-08-10 16:31:28 +03:00
TonCherAmi 153aaf7608
Add access denied resolver (#42) 2021-08-09 12:29:11 +03:00
TonCherAmi 91d4dd66da
Add scope authority prefix (#41) 2021-08-08 22:19:05 +03:00
Alexander Buntakov a95b1ac4fd
split jwt issuer and verifier (#40) 2021-08-02 16:06:42 +03:00
TonCherAmi e9a8c86515
Merge pull request #39 from TouchInstinct/fix/doc
Fix keypair generation instructions
2021-07-30 12:30:09 +03:00
TonCherAmi 89c0b4afd1
Fix keypair generation instructions 2021-07-30 12:20:10 +03:00
Mikhail Yasnov aa2451e12a
Merge pull request #38 from TouchInstinct/feature/refresh-token-hash
Hash refresh token values
2021-07-20 15:42:28 +03:00
Mikhail Yasnov d684b13f37 Use algorithm SHA256 2021-07-20 15:26:09 +03:00
Mikhail Yasnov 3a284c8905 Fix refresh token hash logic 2021-07-20 14:31:31 +03:00
Mikhail Yasnov 6ea1bf2b65 Hash refresh token values 2021-07-20 13:33:20 +03:00
Mikhail Yasnov de1778463a
Merge pull request #37 from TouchInstinct/fix/json-ignore-error
Ignore LogData error when convert to json
2021-07-19 18:58:03 +03:00
Mikhail Yasnov 7d555d00b2 Ignore LogData error when convert to json 2021-07-19 18:13:26 +03:00
Mikhail Yasnov 8d5719fa1e
Merge pull request #36 from TouchInstinct/fix/logger-context-update
Fix LoggerExecutionContext update
2021-07-19 12:34:21 +03:00
Mikhail Yasnov 80bc2e4dfa Fix LoggerExecutionContext update 2021-07-19 12:31:18 +03:00
Mikhail Yasnov 6810db34d1
Merge pull request #34 from TouchInstinct/feature/update-user-service
Add logout and password update methods
2021-07-15 13:23:08 +03:00
Mikhail Yasnov ba35eb76c2 Override equals and hashCode for DeviceEntity 2021-07-15 13:21:35 +03:00
Mikhail Yasnov 8463eea777 Fix user devices counter after login and logout 2021-07-15 13:21:35 +03:00
Mikhail Yasnov 6dd6be5a91
Merge pull request #35 from TouchInstinct/fix/not-found-resolver
Fix NotFoundExceptionResolver
2021-07-12 18:22:19 +03:00
Mikhail Yasnov a54af3cef4 Fix NotFoundExceptionResolver 2021-07-12 18:17:29 +03:00
Mikhail Yasnov 1f5b98b80d Add UserCoreService method for password update 2021-07-08 18:01:27 +03:00
Mikhail Yasnov 2eeab2dade Add UserCoreService logout method 2021-07-08 16:51:26 +03:00
Mikhail Yasnov 0de74ca3a8
Add method to get username by user id and identifier type (#33)
* Add method to get username by user id

* Fix getUserAccountOrNull method name

* Get user account by user id
2021-07-06 14:11:39 +03:00
Mikhail Yasnov 2d75a4a991
Merge pull request #32 from TouchInstinct/fix/refresh-method
Rewrite refresh token method
2021-07-02 16:43:08 +03:00
Mikhail Yasnov 4bc0a10106 Rewrite refresh token method 2021-07-02 16:27:23 +03:00
Mikhail Yasnov 8c6b6a29aa
Merge pull request #31 from TouchInstinct/feature/refresh-token-method
Add method to mark token refreshed
2021-07-02 15:00:30 +03:00
Mikhail Yasnov 1dde5efd61 Rename RefreshToken usedAt field 2021-07-02 14:57:29 +03:00
Mikhail Yasnov dcc35b3b10 Add method to mark token refreshed 2021-07-02 14:43:42 +03:00
Alexander Buntakov 915dbe9301
Merge pull request #30 from TouchInstinct/fix/refresh-tokens
fix migrations, refresh token
2021-06-27 10:28:25 +03:00
Alexander Buntakov bf53ebf369 fix migrations, refresh token 2021-06-27 10:23:26 +03:00
Alexander Buntakov 7366a93808
Merge pull request #29 from TouchInstinct/fix/liquebase
fix liquibase resource accessor
2021-06-26 16:39:12 +03:00
Alexander Buntakov b4ec699e3b fix liquibase resource accessor 2021-06-26 15:55:47 +03:00
Mikhail Yasnov 8e9924afb7
Merge pull request #28 from TouchInstinct/feature/add-web-platform
Add device platform for Web
2021-06-22 14:22:19 +03:00
Mikhail Yasnov 899b91edb6 Add device platform for Web 2021-06-22 14:14:33 +03:00
Mikhail Yasnov c50bf4df50
Merge pull request #27 from TouchInstinct/feature/spring-validation
Create spring validation module
2021-06-21 20:57:16 +03:00
Mikhail Yasnov 4f61e105a7 Move default values to constants 2021-06-21 20:33:31 +03:00
Mikhail Yasnov 79ae4b8b65 Create spring validation module 2021-06-21 19:24:01 +03:00
Alexander Buntakov facff6525d
Merge pull request #26 from TouchInstinct/feature/hash-utils
add hash utils
2021-06-20 15:51:38 +03:00
Alexander Buntakov be38ebf451
Update common/src/main/kotlin/ru/touchin/common/security/hash/HashUtils.kt
Co-authored-by: TonCherAmi <vasili.karaev@protonmail.com>
2021-06-20 15:40:00 +03:00
Alexander Buntakov 4f742e9235 add hash utils 2021-06-20 15:36:57 +03:00
Mikhail Yasnov 5daf28ac10
Merge pull request #25 from TouchInstinct/feature/not-found-exp-handler
Create error handler for CommonNotFoundException
2021-06-16 23:35:48 +03:00
Mikhail Yasnov 6a08c4568b Simplify conditional expression 2021-06-16 21:42:07 +03:00
Mikhail Yasnov 11fcb7a586 Create error handler for CommonNotFoundException 2021-06-16 21:07:09 +03:00
Alexander Buntakov fd4c32042c
Merge pull request #24 from TouchInstinct/fix/handler-logger
add @EnableSpringExceptionHandlerLogger
2021-06-15 20:51:43 +03:00
Alexander Buntakov 131c7bd573 add @EnableSpringExceptionHandlerLogger 2021-06-15 20:31:58 +03:00
Alexander Buntakov 347ff03670
Merge pull request #23 from TouchInstinct/fix/security
fix security modules
2021-06-15 19:09:05 +03:00
Alexander Buntakov 0c39aedea9 fix linter 2021-06-15 19:06:50 +03:00
Alexander Buntakov 67adec4d92 move auth migration to single schema 2021-06-15 19:00:06 +03:00
Alexander Buntakov 5e84eb2bb3 fix security modules 2021-06-15 00:14:21 +03:00
Alexander Buntakov 6f703d0bfc
Merge pull request #22 from TouchInstinct/feature/auth
add auth module
2021-06-14 17:21:12 +03:00
391 changed files with 9783 additions and 229 deletions

328
README.md
View File

@ -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
```

View File

@ -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

View File

@ -1,5 +0,0 @@
package ru.touchin.auth.core.device.dto.enums
enum class DevicePlatform {
Android, Huawei, Apple
}

View File

@ -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
}

View File

@ -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")
}

15
captcha/build.gradle.kts Normal file
View File

@ -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")
}

View File

@ -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
)

View File

@ -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"
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
)

View File

@ -0,0 +1,7 @@
package ru.touchin.captcha.exceptions
import ru.touchin.common.exceptions.CommonException
class CaptchaActionMismatchException(
action: String
) : CommonException("invalid captcha action $action")

View File

@ -0,0 +1,5 @@
package ru.touchin.captcha.exceptions
import ru.touchin.common.exceptions.CommonException
class CaptchaResponseMissingException : CommonException("missing captcha response header")

View File

@ -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")

View File

@ -0,0 +1,7 @@
package ru.touchin.captcha.exceptions
import ru.touchin.common.exceptions.CommonException
class CaptchaSiteVerifyFailureException(
description: String?
) : CommonException(description)

View File

@ -0,0 +1,7 @@
package ru.touchin.captcha.exceptions
import ru.touchin.common.exceptions.CommonException
class CaptchaUnknownActionException(
action: String
) : CommonException("unknown captcha action $action")

View File

@ -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>
)

View File

@ -0,0 +1,9 @@
package ru.touchin.captcha.services
import ru.touchin.captcha.dto.CaptchaVerificationResult
interface CaptchaService {
fun verify(response: String): CaptchaVerificationResult
}

View File

@ -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,
)
}
}

View File

@ -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")
}
}
}

View File

@ -0,0 +1,8 @@
plugins {
id("kotlin")
}
dependencies {
implementation(project(":common"))
implementation("com.tngtech.archunit:archunit")
}

View File

@ -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")
}

View File

@ -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'")
}

View File

@ -0,0 +1,7 @@
plugins {
id("kotlin")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

View File

@ -0,0 +1,5 @@
package ru.touchin.common.devices.enums
enum class DevicePlatform {
Android, Huawei, Apple, Web
}

View File

@ -1,4 +1,4 @@
@file:Suppress("unused")
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package ru.touchin.common.measure

View File

@ -0,0 +1,8 @@
plugins {
id("kotlin")
id("kotlin-spring")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

View File

@ -0,0 +1,9 @@
package ru.touchin.common.messaging
import java.time.ZonedDateTime
interface BaseEvent {
val firedAt: ZonedDateTime
}

View File

@ -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")

View File

@ -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
}

View File

@ -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())
}
}
}

View File

@ -13,7 +13,7 @@ import javax.persistence.MappedSuperclass
@MappedSuperclass
abstract class BaseEntity : Serializable {
@CreatedDate
@CreatedDate(updatable = false)
lateinit var createdAt: ZonedDateTime
@LastModifiedDate

View File

@ -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
}
}

View File

@ -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"
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("unused")
package ru.touchin.common.spring.security.auditor
import org.springframework.core.annotation.Order

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -0,0 +1,8 @@
plugins {
id("kotlin")
id("kotlin-spring")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

View File

@ -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"),
}

View File

@ -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()
}
}

View File

@ -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

View File

@ -0,0 +1,8 @@
package ru.touchin.common.page
interface GetPage {
val page: Int
val size: Int
}

View File

@ -0,0 +1,8 @@
package ru.touchin.common.page
interface PageInfo {
val pageCount: Int
val totalItemCount: Long
}

View File

@ -24,4 +24,6 @@ object SecureRandomStringGenerator {
}
}
fun generateRandomBytes(size: Int) = ByteArray(size).also(secureRandom::nextBytes)
}

View File

@ -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))
}
}

View File

@ -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()
}

402
detekt-config.yml Normal file
View File

@ -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.*'

536
diktat-analysis.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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

View File

@ -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

View File

@ -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,
)
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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")

View File

@ -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
)
}
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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,
)

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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"))

View File

@ -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"))

View File

@ -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
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -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")

View File

@ -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
}

View File

@ -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)
}

View File

@ -0,0 +1,9 @@
package ru.touchin.geoip.core.services
import ru.touchin.common.territories.enums.TerritoryCode
interface GeoipLookupService {
fun get(ipAddress: String): TerritoryCode
}

View File

@ -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
}
}

View File

@ -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");

View File

@ -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")
);

View File

@ -0,0 +1,3 @@
databaseChangeLog:
- includeAll:
path: classpath*:/geoip/db/changelog/core

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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"))

View File

@ -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)
}
}
}

View File

@ -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> {

View File

@ -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")

View File

@ -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")
}

View File

@ -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

View File

@ -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()

View File

@ -1,6 +1,6 @@
package ru.touchin.logger.context
@Suppress("unused", "EnumEntryName")
@Suppress("unused", "EnumEntryName", "EnumNaming")
enum class DefaultContextFields {
id,
host,

View File

@ -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

View File

@ -1,5 +1,5 @@
package ru.touchin.logger.dto
enum class LogLevel {
Trace, Info, Error
Trace, Debug, Info, Error
}

View File

@ -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