diff --git a/.github/settings.yml b/.github/settings.yml
new file mode 100644
index 0000000..9267e7d
--- /dev/null
+++ b/.github/settings.yml
@@ -0,0 +1,2 @@
+# These settings are synced to GitHub by https://probot.github.io/apps/settings/
+_extends: .github
diff --git a/.github/workflows/basic-linters.yml b/.github/workflows/basic-linters.yml
new file mode 100644
index 0000000..6114f14
--- /dev/null
+++ b/.github/workflows/basic-linters.yml
@@ -0,0 +1,10 @@
+name: Vality basic linters
+
+on:
+ pull_request:
+ branches:
+ - "*"
+
+jobs:
+ lint:
+ uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..424e109
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,10 @@
+name: Build Maven Artifact
+
+on:
+ pull_request:
+ branches:
+ - '*'
+
+jobs:
+ build:
+ uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v2
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..a4192d7
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,13 @@
+name: Maven Deploy Artifact
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ build-and-deploy:
+ uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v2
+ secrets:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ mm-webhook-url: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..721f9b9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,78 @@
+# Created by .ignore support plugin (hsz.mobi)
+.eunit
+deps
+*.o
+*.beam
+*.plt
+erl_crash.dump
+ebin/*.beam
+rel/example_project
+.concrete/DEV_MODE
+.rebar
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/dictionaries
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
+
+# Sensitive or high-churn files:
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+*.iws
+*.ipr
+*.iml
+
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+env.list
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..03dfd73
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,321 @@
+
+
+ 4.0.0
+
+
+ dev.vality
+ service-parent-pom
+ 2.1.4
+
+
+ alert-tg-bot
+ 1.0-SNAPSHOT
+
+
+ UTF-8
+ UTF-8
+ 17
+ 8022
+ 8023
+ ${server.port} ${management.port}
+ alert_tg_bot
+ 5432
+ jdbc:postgresql://localhost:${db.port}/${db.name}
+ postgres
+ postgres
+ alert_tg_bot
+ 1.4.3
+
+
+
+
+
+ dev.vality
+ mayday-proto
+ 1.5-5015827
+
+
+ dev.vality
+ alert-tg-bot-proto
+ 1.4-adf83ed
+
+
+ dev.vality
+ db-common-lib
+
+
+ dev.vality.geck
+ serializer
+
+
+ dev.vality.geck
+ common
+
+
+ dev.vality.geck
+ filter
+
+
+ dev.vality.geck
+ migrator
+
+
+ dev.vality
+ adapter-common-lib
+ 1.2.8
+
+
+ dev.vality
+ damsel
+ 1.597-bfedcb9
+
+
+
+
+ org.springframework.vault
+ spring-vault-core
+ 2.3.2
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.hibernate
+ hibernate-validator
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-jooq
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ io.micrometer
+ micrometer-core
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+
+
+ org.telegram
+ telegrambots
+ 6.5.0
+
+
+ org.telegram
+ telegrambots-spring-boot-starter
+ 6.5.0
+
+
+ org.flywaydb
+ flyway-core
+
+
+ com.zaxxer
+ HikariCP
+
+
+ org.postgresql
+ postgresql
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ dev.vality
+ testcontainers-annotations
+ ${testcontainers.annotations.version}
+ test
+
+
+
+
+
+
+ ${project.build.directory}/maven-shared-archive-resources
+ ${project.build.directory}
+
+ Dockerfile
+
+ true
+
+
+ ${project.build.directory}/maven-shared-archive-resources
+ true
+
+ Dockerfile
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-remote-resources-plugin
+ 1.6.0
+
+
+ org.apache.maven.shared
+ maven-filtering
+ 1.3
+
+
+
+
+ dev.vality:shared-resources:${shared-resources.version}
+
+ false
+ false
+
+
+
+
+ process
+
+
+
+
+
+ org.flywaydb
+ flyway-maven-plugin
+
+ ${db.url}
+ ${db.user}
+ ${db.password}
+
+ ${db.schema}
+
+
+ filesystem:${project.basedir}/src/main/resources/db/migration
+
+
+
+
+ migrate
+ generate-sources
+
+ migrate
+
+
+
+
+
+ org.jooq
+ jooq-codegen-maven
+
+
+ org.postgresql.Driver
+ ${db.url}
+ ${db.user}
+ ${db.password}
+
+
+
+ true
+ true
+ true
+ true
+
+
+ org.jooq.meta.postgres.PostgresDatabase
+ .*
+ schema_version|flyway_schema_history
+ ${db.schema}
+
+
+ dev.vality.alert.tg.bot.domain
+ target/generated-sources/jooq
+
+
+
+
+
+ gen-src
+ generate-sources
+
+ generate
+
+
+
+
+
+ dev.vality.maven.plugins
+ pg-embedded-plugin
+ 1.0.1
+
+ ${db.port}
+ ${db.name}
+
+ ${db.schema}
+
+
+
+
+ PG_server_start
+ initialize
+
+ start
+
+
+
+ PG_server_stop
+ compile
+
+ stop
+
+
+
+
+
+
+
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..a20bfd6
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": ["local>valitydev/.github:renovate-config"]
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java b/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java
new file mode 100644
index 0000000..e8961b2
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java
@@ -0,0 +1,15 @@
+package dev.vality.alert.tg.bot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+
+@ServletComponentScan
+@SpringBootApplication
+public class AlertTgBotApplication extends SpringApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AlertTgBotApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java b/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java
new file mode 100644
index 0000000..b14f620
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java
@@ -0,0 +1,21 @@
+package dev.vality.alert.tg.bot.config;
+
+import dev.vality.alert.tg.bot.config.properties.MayDayProperties;
+import dev.vality.alerting.mayday.AlertingServiceSrv;
+import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+
+@Configuration
+public class MayDayConfig {
+
+ @Bean
+ public AlertingServiceSrv.Iface mayDayClient(MayDayProperties properties) throws IOException {
+ return new THSpawnClientBuilder()
+ .withAddress(properties.getUrl().getURI())
+ .withNetworkTimeout(properties.getNetworkTimeout())
+ .build(AlertingServiceSrv.Iface.class);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java b/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java
new file mode 100644
index 0000000..cfbe1d9
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java
@@ -0,0 +1,24 @@
+package dev.vality.alert.tg.bot.config.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+
+@Configuration
+@ConfigurationProperties(prefix = "bot")
+@Validated
+@Getter
+@Setter
+public class AlertBotProperties {
+
+ @NotNull
+ private String token;
+ @NotNull
+ private String name;
+ @NotNull
+ private String chatId;
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java b/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java
new file mode 100644
index 0000000..9ba04a1
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java
@@ -0,0 +1,23 @@
+package dev.vality.alert.tg.bot.config.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+
+@Configuration
+@ConfigurationProperties(prefix = "mayday")
+@Validated
+@Getter
+@Setter
+public class MayDayProperties {
+
+ @NotNull
+ private Resource url;
+ @NotNull
+ private int networkTimeout = 5000;
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java b/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java
new file mode 100644
index 0000000..fd95eab
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java
@@ -0,0 +1,28 @@
+package dev.vality.alert.tg.bot.constants;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+@Getter
+@RequiredArgsConstructor
+public enum MainMenu {
+
+ CREATE_ALERT("Создать новый алерт", "createAlert"),
+ GET_ALL_ALERTS("Получить все созданные алерты", "getAllAlerts"),
+ DELETE_ALERT("Удалить алерт", "deleteAlert"),
+ RETURN_TO_MENU("Возврат в меню", "return"),
+ DELETE_ALL_ALERTS("Удалить все алерты", "deleteAllAlerts"),
+ CONFIGURE_PARAM("CONFIGURE_PARAM", "CONFIGURE_PARAM");
+
+ private final String text;
+
+ private final String callbackData;
+
+ public static MainMenu valueOfCallbackData(String callbackData) {
+ return Arrays.stream(MainMenu.values()).filter(mainMenu -> mainMenu.getCallbackData().equals(callbackData))
+ .findFirst().orElse(CONFIGURE_PARAM);
+ }
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java b/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java
new file mode 100644
index 0000000..3266e35
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java
@@ -0,0 +1,23 @@
+package dev.vality.alert.tg.bot.constants;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum TextConstants {
+
+ SELECT_ACTION("Выберите действие"),
+ REPLY_OR_ACTION("Ответьте на предыдущее сообщение или выберите действие"),
+ ENTER_PARAMETER("Введите параметр %s в формате %s"),
+ ALERT_CREATED("Алерт создан"),
+ ENTER_ALERT_ID_FOR_REMOVED("Введите id алерта для удаления"),
+ DELETE_ALERT("Удалить алерт"),
+ SELECT_ALERT("Выберите алерт"),
+ ALERT_REMOVED("Алерт удален"),
+ ALERTS_REMOVED("Алерты удалены"),
+ DELETE_ALL_ALERTS("Удалить все алерты");
+
+ private final String text;
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java b/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java
new file mode 100644
index 0000000..a9eb034
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java
@@ -0,0 +1,24 @@
+package dev.vality.alert.tg.bot.constants;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Set;
+
+@Getter
+@RequiredArgsConstructor
+public enum UserStatuses {
+
+ CREATOR("creator"),
+ ADMINISTRATOR("administrator"),
+ MEMBER("member");
+
+ private final String value;
+
+ public static final Set ALLOWED_USER_STATUSES =
+ Set.of(
+ CREATOR.getValue(),
+ ADMINISTRATOR.getValue(),
+ MEMBER.getValue()
+ );
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java
new file mode 100644
index 0000000..81351cd
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java
@@ -0,0 +1,12 @@
+package dev.vality.alert.tg.bot.dao;
+
+import dev.vality.dao.impl.AbstractGenericDao;
+
+import javax.sql.DataSource;
+
+public class AbstractDao extends AbstractGenericDao {
+
+ public AbstractDao(DataSource dataSource) {
+ super(dataSource);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java
new file mode 100644
index 0000000..98b117a
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java
@@ -0,0 +1,11 @@
+package dev.vality.alert.tg.bot.dao;
+
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+
+public interface ParametersDao {
+
+ ParametersData save(ParametersData parametersData);
+
+ ParametersData getByAlertIdAndParamName(String alertId, String paramName);
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java
new file mode 100644
index 0000000..1f1ef7f
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java
@@ -0,0 +1,13 @@
+package dev.vality.alert.tg.bot.dao;
+
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+
+public interface StateDataDao {
+
+ StateData save(StateData data);
+
+ void updateParams(Long userId, String params);
+
+ StateData getByUserId(Long userId);
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java b/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java
new file mode 100644
index 0000000..0dbfc53
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java
@@ -0,0 +1,55 @@
+package dev.vality.alert.tg.bot.dao.impl;
+
+
+import dev.vality.alert.tg.bot.dao.AbstractDao;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+import dev.vality.mapper.RecordRowMapper;
+import org.jooq.Query;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.util.Optional;
+
+import static dev.vality.alert.tg.bot.domain.Tables.PARAMETERS_DATA;
+
+
+@Component
+public class ParametersDaoImpl extends AbstractDao implements ParametersDao {
+
+ private final RecordRowMapper parameterTypeRecordRowMapper;
+
+ public ParametersDaoImpl(DataSource dataSource) {
+ super(dataSource);
+ parameterTypeRecordRowMapper = new RecordRowMapper<>(PARAMETERS_DATA, ParametersData.class);
+ }
+
+ @Override
+ public ParametersData save(ParametersData parametersData) {
+ Query query = getDslContext()
+ .insertInto(PARAMETERS_DATA)
+ .set(getDslContext().newRecord(PARAMETERS_DATA, parametersData))
+ .onConflict(PARAMETERS_DATA.PARAM_ID)
+ .doUpdate()
+ .set(getDslContext().newRecord(PARAMETERS_DATA, parametersData))
+ .returning(PARAMETERS_DATA.ID);
+ GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
+ execute(query, keyHolder);
+ Long id = Optional.ofNullable(keyHolder.getKey())
+ .map(Number::longValue)
+ .orElseThrow(() -> new IllegalStateException("Can't get data id"));
+ parametersData.setId(id);
+ return parametersData;
+ }
+
+
+ @Override
+ public ParametersData getByAlertIdAndParamName(String alertId, String paramName) {
+ var get = getDslContext()
+ .selectFrom(PARAMETERS_DATA)
+ .where(PARAMETERS_DATA.ALERT_ID.eq(alertId))
+ .and(PARAMETERS_DATA.PARAM_NAME.eq(paramName));
+ return fetchOne(get, parameterTypeRecordRowMapper);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java b/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java
new file mode 100644
index 0000000..d6e4ba5
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java
@@ -0,0 +1,64 @@
+package dev.vality.alert.tg.bot.dao.impl;
+
+
+import dev.vality.alert.tg.bot.dao.AbstractDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.domain.tables.records.StateDataRecord;
+import dev.vality.mapper.RecordRowMapper;
+import org.jooq.Query;
+import org.jooq.UpdateConditionStep;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.util.Optional;
+
+import static dev.vality.alert.tg.bot.domain.Tables.STATE_DATA;
+
+
+@Component
+public class StateDataDaoImpl extends AbstractDao implements StateDataDao {
+
+ private final RecordRowMapper stateDataRecordRowMapper;
+
+ public StateDataDaoImpl(DataSource dataSource) {
+ super(dataSource);
+ stateDataRecordRowMapper = new RecordRowMapper<>(STATE_DATA, StateData.class);
+ }
+
+ @Override
+ public StateData save(StateData data) {
+ Query query = getDslContext()
+ .insertInto(STATE_DATA)
+ .set(getDslContext().newRecord(STATE_DATA, data))
+ .onConflict(STATE_DATA.USER_ID)
+ .doUpdate()
+ .set(getDslContext().newRecord(STATE_DATA, data))
+ .returning(STATE_DATA.ID);
+ GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
+ execute(query, keyHolder);
+ Long id = Optional.ofNullable(keyHolder.getKey())
+ .map(Number::longValue)
+ .orElseThrow(() -> new IllegalStateException("Can't get data id"));
+ data.setId(id);
+ return data;
+ }
+
+ @Override
+ public void updateParams(Long userId, String params) {
+ UpdateConditionStep update = getDslContext()
+ .update(STATE_DATA)
+ .set(STATE_DATA.MAP_PARAMS, params)
+ .where(STATE_DATA.USER_ID.eq(userId));
+ execute(update);
+ }
+
+ @Override
+ public StateData getByUserId(Long userId) {
+ var get = getDslContext()
+ .selectFrom(STATE_DATA)
+ .where(STATE_DATA.USER_ID.eq(userId));
+ return fetchOne(get, stateDataRecordRowMapper);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java b/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java
new file mode 100644
index 0000000..860ad4b
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java
@@ -0,0 +1,12 @@
+package dev.vality.alert.tg.bot.exeptions;
+
+public class AlertTgBotException extends RuntimeException {
+
+ public AlertTgBotException(String message) {
+ super(message);
+ }
+
+ public AlertTgBotException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java b/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java
new file mode 100644
index 0000000..a518b2a
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java
@@ -0,0 +1,8 @@
+package dev.vality.alert.tg.bot.exeptions;
+
+public class UnknownHandlerException extends RuntimeException {
+
+ public UnknownHandlerException() {
+ super("Unknown handler");
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java
new file mode 100644
index 0000000..778f136
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java
@@ -0,0 +1,41 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.constants.MainMenu;
+import dev.vality.alert.tg.bot.exeptions.AlertTgBotException;
+import dev.vality.alert.tg.bot.mapper.MenuCallbackMapper;
+import dev.vality.alert.tg.bot.mapper.ParametersCallbackMapper;
+import lombok.RequiredArgsConstructor;
+import org.apache.thrift.TException;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+@Component
+@RequiredArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class CallbackHandler implements CommonHandler {
+
+ private final MenuCallbackMapper menuCallbackMapper;
+ private final ParametersCallbackMapper parametersCallbackMapper;
+
+ @Override
+ public boolean filter(Update update) {
+ return update.hasCallbackQuery();
+ }
+
+ @Override
+ public SendMessage handle(Update update, long userId) throws TException {
+ String callbackData = update.getCallbackQuery().getData();
+ return switch (MainMenu.valueOfCallbackData(callbackData)) {
+ case CREATE_ALERT -> menuCallbackMapper.createAlertCallback(userId);
+ case GET_ALL_ALERTS -> menuCallbackMapper.getAllAlertsCallback(userId);
+ case DELETE_ALERT -> menuCallbackMapper.deleteAlertCallback(userId);
+ case DELETE_ALL_ALERTS -> menuCallbackMapper.deleteAllAlertsCallback(userId);
+ case RETURN_TO_MENU -> menuCallbackMapper.returnCallback();
+ case CONFIGURE_PARAM -> parametersCallbackMapper.mapParametersCallback(callbackData, userId);
+ };
+ }
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java
new file mode 100644
index 0000000..d01eb2a
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java
@@ -0,0 +1,13 @@
+package dev.vality.alert.tg.bot.handler;
+
+import org.apache.thrift.TException;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+public interface CommonHandler {
+
+ boolean filter(final Update update);
+
+ SendMessage handle(Update update, long userId) throws TException;
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java
new file mode 100644
index 0000000..36537c5
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java
@@ -0,0 +1,34 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.constants.TextConstants;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import java.util.Objects;
+
+import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup;
+
+@Component
+@Order(Ordered.LOWEST_PRECEDENCE)
+public class MainMenuHandler implements CommonHandler {
+
+ @Override
+ public boolean filter(Update update) {
+ return true;
+ }
+
+ @Override
+ public SendMessage handle(Update update, long userId) {
+ if (Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId())) {
+ SendMessage message = new SendMessage();
+ message.setChatId(userId);
+ message.setText(TextConstants.REPLY_OR_ACTION.getText());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java
new file mode 100644
index 0000000..6bfceee
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java
@@ -0,0 +1,43 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.constants.TextConstants;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import java.util.Objects;
+
+import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup;
+
+@Component
+@RequiredArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class MessageHandler implements CommonHandler {
+
+ private final StateDataDao stateDataDao;
+
+ @Override
+ public boolean filter(Update update) {
+ return update.hasMessage()
+ && update.getMessage().hasText()
+ && update.getMessage().getReplyToMessage() == null
+ && Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId());
+ }
+
+ @Override
+ public SendMessage handle(Update update, long userId) {
+ SendMessage message = new SendMessage();
+ message.setText(TextConstants.SELECT_ACTION.getText());
+ StateData stateData = new StateData();
+ stateData.setUserId(userId);
+ stateDataDao.save(stateData);
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java
new file mode 100644
index 0000000..9df85ca
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java
@@ -0,0 +1,69 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.constants.TextConstants;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.mapper.JsonMapper;
+import dev.vality.alert.tg.bot.mapper.ReplyMessagesMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import java.util.Map;
+import java.util.Objects;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class ReplyHandler implements CommonHandler {
+
+ private final StateDataDao stateDataDao;
+ private final ReplyMessagesMapper replyMessagesMapper;
+ private final JsonMapper jsonMapper;
+
+ @Override
+ public boolean filter(Update update) {
+ return update.hasMessage()
+ && update.getMessage().getReplyToMessage() != null
+ && Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId());
+ }
+
+ @Override
+ public SendMessage handle(Update update, long userId) throws TException {
+ if (isReplyMessageDeleteAlert(update)) {
+ log.info("Delete alert {} for user {}", update.getMessage().getText(), userId);
+ return replyMessagesMapper.deleteAlert(update.getMessage().getText());
+ } else {
+ StateData stateData = stateDataDao.getByUserId(userId);
+ Map paramMap = jsonMapper.toMap(stateData.getMapParams());
+ paramMap.put(update.getMessage().getReplyToMessage().getText(), update.getMessage().getText());
+ stateData.setMapParams(jsonMapper.toJson(paramMap));
+ stateDataDao.updateParams(userId, stateData.getMapParams());
+ String nextKey = getNextKeyForFill(paramMap);
+ if (nextKey != null) {
+ return replyMessagesMapper.createNextParameterRequest(nextKey);
+ } else {
+ return replyMessagesMapper.createAlertRequest(userId);
+ }
+ }
+ }
+
+ private boolean isReplyMessageDeleteAlert(Update update) {
+ return update.getMessage().getReplyToMessage().getText()
+ .equals(TextConstants.ENTER_ALERT_ID_FOR_REMOVED.getText());
+ }
+
+ private static String getNextKeyForFill(Map map) {
+ return map.entrySet().stream()
+ .filter(entry -> null == entry.getValue())
+ .findFirst().map(Map.Entry::getKey)
+ .orElse(null);
+ }
+}
+
diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java
new file mode 100644
index 0000000..e8c43e3
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java
@@ -0,0 +1,28 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class JsonMapper {
+
+ private final ObjectMapper objectMapper;
+
+ @SneakyThrows(JsonProcessingException.class)
+ public String toJson(Object data) {
+ return objectMapper.writeValueAsString(data);
+ }
+
+ @SneakyThrows(JsonProcessingException.class)
+ public Map toMap(String json) {
+ return objectMapper.readValue(json, new TypeReference<>() {
+ });
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java
new file mode 100644
index 0000000..0a1ad49
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java
@@ -0,0 +1,94 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.constants.MainMenu;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.Alert;
+import dev.vality.alerting.mayday.UserAlert;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.constants.TextConstants.*;
+import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MenuCallbackMapper {
+
+ private final MayDayService mayDayService;
+ private final StateDataDao stateDataDao;
+
+ public SendMessage createAlertCallback(long userId) throws TException {
+ StateData stateData = new StateData();
+ stateData.setUserId(userId);
+ stateDataDao.save(stateData);
+ SendMessage message = new SendMessage();
+ message.setText(SELECT_ALERT.getText());
+ List alerts = mayDayService.getSupportedAlerts();
+ List> rowsInline = new ArrayList<>();
+ alerts.forEach(alert -> rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(alert.getName())
+ .callbackData(alert.getAlert())
+ .build())));
+ rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(MainMenu.RETURN_TO_MENU.getText())
+ .callbackData(MainMenu.RETURN_TO_MENU.getCallbackData())
+ .build()));
+ List rowInline = new ArrayList<>();
+ rowsInline.add(rowInline);
+ InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup();
+ markupInline.setKeyboard(rowsInline);
+ message.setReplyMarkup(markupInline);
+ return message;
+ }
+
+ public SendMessage getAllAlertsCallback(long userId) throws TException {
+ SendMessage message = new SendMessage();
+ List userAlerts = mayDayService.getUserAlerts(String.valueOf(userId));
+ StringBuilder text = new StringBuilder("Ваши алерты:\n");
+ userAlerts.forEach(userAlert -> {
+ text.append("id: ").append(userAlert.getId())
+ .append(" Название: ").append(userAlert.getName())
+ .append("\n");
+ });
+ message.setText(text.toString());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+
+ public SendMessage deleteAlertCallback(long userId) throws TException {
+ SendMessage message = new SendMessage();
+ mayDayService.deleteAlert(String.valueOf(userId));
+ message.setText(ENTER_ALERT_ID_FOR_REMOVED.getText());
+ message.setReplyMarkup(new ForceReplyKeyboard());
+ return message;
+ }
+
+ public SendMessage deleteAllAlertsCallback(long userId) throws TException {
+ log.info("User {} delete all alerts", userId);
+ SendMessage message = new SendMessage();
+ mayDayService.deleteAllAlerts(String.valueOf(userId));
+ message.setText(ALERTS_REMOVED.getText());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+
+ public SendMessage returnCallback() {
+ SendMessage message = new SendMessage();
+ message.setText(SELECT_ACTION.getText());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java
new file mode 100644
index 0000000..dc0c5af
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java
@@ -0,0 +1,71 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.enums.ParameterType;
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.AlertConfiguration;
+import dev.vality.alerting.mayday.ParameterConfiguration;
+import lombok.RequiredArgsConstructor;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class ParametersCallbackMapper {
+
+ private final MayDayService mayDayService;
+ private final StateDataDao stateDataDao;
+ private final ParametersDao parametersDao;
+ private final JsonMapper jsonMapper;
+
+ public SendMessage mapParametersCallback(String callData, long userId) throws TException {
+ SendMessage message = new SendMessage();
+ AlertConfiguration alertConfiguration = mayDayService.getAlertConfiguration(callData);
+ String alertId = alertConfiguration.getAlertId();
+ List parameterConfigurations = alertConfiguration.getParameters();
+ fillStateDataAndSave(userId, alertId, parameterConfigurations);
+ parameterConfigurations.forEach(param -> convertParameterConfigurationsAndSave(alertId, param));
+ message.setText(parameterConfigurations.get(0).getName());
+ message.setReplyMarkup(new ForceReplyKeyboard());
+ return message;
+ }
+
+ private void fillStateDataAndSave(long userId, String alertId,
+ List parameterConfigurations) {
+ StateData stateData = stateDataDao.getByUserId(userId);
+ stateData.setAlertId(alertId);
+ Map mapParams = new HashMap<>();
+ parameterConfigurations.forEach(param -> {
+ mapParams.put(param.getName(), null);
+ });
+ stateData.setMapParams(jsonMapper.toJson(mapParams));
+ stateDataDao.save(stateData);
+ }
+
+ private void convertParameterConfigurationsAndSave(String alertId, ParameterConfiguration param) {
+ ParametersData parametersData = new ParametersData();
+ parametersData.setAlertId(alertId);
+ parametersData.setParamId(param.getId());
+ parametersData.setParamName(param.getName());
+ parametersData.setParamType(getParamType(param.getType()));
+ parametersDao.save(parametersData);
+ }
+
+ private ParameterType getParamType(dev.vality.alerting.mayday.ParameterType parameterType) {
+ return switch (parameterType) {
+ case integer -> ParameterType.integer;
+ case str -> ParameterType.str;
+ case bl -> ParameterType.bl;
+ case fl -> ParameterType.fl;
+ };
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java
new file mode 100644
index 0000000..f665325
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java
@@ -0,0 +1,82 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.constants.TextConstants;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.CreateAlertRequest;
+import dev.vality.alerting.mayday.ParameterInfo;
+import dev.vality.alerting.mayday.ParameterValue;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ReplyMessagesMapper {
+
+ private final MayDayService mayDayService;
+ private final ParametersDao parametersDao;
+ private final StateDataDao stateDataDao;
+ private final JsonMapper jsonMapper;
+
+ public SendMessage createAlertRequest(long userId) throws TException {
+ StateData stateData = stateDataDao.getByUserId(userId);
+ log.info("Start create alert with stateData {}", stateData);
+ Map paramMap = jsonMapper.toMap(stateData.getMapParams());
+ CreateAlertRequest createAlertRequest = new CreateAlertRequest();
+ createAlertRequest.setAlertId(stateData.getAlertId());
+ createAlertRequest.setUserId(String.valueOf(userId));
+ List parameterInfos = new ArrayList<>();
+ for (String key : paramMap.keySet()) {
+ ParametersData parametersData = parametersDao.getByAlertIdAndParamName(stateData.getAlertId(), key);
+ ParameterInfo parameterInfo = new ParameterInfo();
+ parameterInfo.setParameterId(parametersData.getParamId());
+ parameterInfo.setType(mapParameterValue(parametersData, paramMap, key));
+ parameterInfos.add(parameterInfo);
+ }
+ createAlertRequest.setParameters(parameterInfos);
+ mayDayService.createAlert(createAlertRequest);
+ SendMessage message = new SendMessage();
+ message.setText(TextConstants.ALERT_CREATED.getText());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ log.info("Alert {} was created", createAlertRequest);
+ return message;
+ }
+
+ public SendMessage createNextParameterRequest(String text) {
+ SendMessage message = new SendMessage();
+ message.setReplyMarkup(new ForceReplyKeyboard());
+ message.setText(text);
+ return message;
+ }
+
+ public SendMessage deleteAlert(String text) throws TException {
+ SendMessage message = new SendMessage();
+ mayDayService.deleteAlert(text);
+ message.setText(TextConstants.ALERT_REMOVED.getText());
+ message.setReplyMarkup(buildMainInlineKeyboardMarkup());
+ return message;
+ }
+
+ private ParameterValue mapParameterValue(ParametersData parametersData, Map paramMap, String key) {
+ return switch (parametersData.getParamType()) {
+ case str -> ParameterValue.str(paramMap.get(key));
+ case integer -> ParameterValue.integer(Long.parseLong(paramMap.get(key)));
+ case fl -> ParameterValue.fl(Double.parseDouble(paramMap.get(key)));
+ case bl -> ParameterValue.bl(Boolean.parseBoolean(paramMap.get(key)));
+ };
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java b/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java
new file mode 100644
index 0000000..b6d997a
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java
@@ -0,0 +1,109 @@
+package dev.vality.alert.tg.bot.service;
+
+import dev.vality.alert.tg.bot.config.properties.AlertBotProperties;
+import dev.vality.alert.tg.bot.exeptions.AlertTgBotException;
+import dev.vality.alert.tg.bot.exeptions.UnknownHandlerException;
+import dev.vality.alert.tg.bot.handler.CommonHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Service;
+import org.telegram.telegrambots.bots.TelegramLongPollingBot;
+import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatMember;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember;
+import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
+
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.constants.UserStatuses.ALLOWED_USER_STATUSES;
+
+@Slf4j
+@Service
+public class AlertBot extends TelegramLongPollingBot {
+
+ private final List handlers;
+ private final AlertBotProperties alertBotProperties;
+ private final MayDayService mayDayService;
+
+ public AlertBot(AlertBotProperties alertBotProperties,
+ List handlers,
+ MayDayService mayDayService) {
+ super(alertBotProperties.getToken());
+ this.handlers = handlers;
+ this.alertBotProperties = alertBotProperties;
+ this.mayDayService = mayDayService;
+ }
+
+ @Override
+ public String getBotUsername() {
+ return alertBotProperties.getName();
+ }
+
+ @Override
+ public void onUpdateReceived(Update update) {
+ try {
+ if (isUserPermission(update)) {
+ long userId = update.hasMessage()
+ ? update.getMessage().getFrom().getId()
+ : update.getCallbackQuery().getFrom().getId();
+ SendMessage message = handlers.stream()
+ .filter(handler -> handler.filter(update))
+ .findFirst()
+ .orElseThrow(UnknownHandlerException::new)
+ .handle(update, userId);
+ if (message != null) {
+ message.setChatId(userId);
+ execute(message);
+ }
+ }
+ } catch (TelegramApiException | TException ex) {
+ throw new AlertTgBotException(String.format("Received an exception while handle update %s", update), ex);
+ }
+ }
+
+ public boolean isUserPermission(Update update) throws TException {
+ Long userId = update.hasMessage()
+ ? update.getMessage().getFrom().getId()
+ : update.getCallbackQuery().getMessage().getFrom().getId();
+ String userName = update.hasMessage()
+ ? update.getMessage().getFrom().getUserName()
+ : update.getCallbackQuery().getMessage().getFrom().getUserName();
+ try {
+ return isChatMemberPermission(userId);
+ } catch (TelegramApiException e) {
+ checkExceptionIsUserInChat(e, userName, userId);
+ throw new AlertTgBotException(String.format("Error while checking user %s permissions", userName), e);
+ }
+ }
+
+ public boolean isChatMemberPermission(Long userId) throws TelegramApiException, TException {
+ ChatMember chatMember = execute(new GetChatMember(alertBotProperties.getChatId(), userId));
+ if (ALLOWED_USER_STATUSES.contains(chatMember.getStatus())) {
+ return true;
+ } else {
+ mayDayService.deleteAllAlerts(String.valueOf(userId));
+ SendMessage message = new SendMessage();
+ message.setChatId(userId);
+ message.setText("У вас не прав на создание алертов, все ранее созданные алерты удалены");
+ execute(message);
+ return false;
+ }
+ }
+
+ private void checkExceptionIsUserInChat(TelegramApiException e, String userName, Long userId) {
+ if (e.getMessage().contains("[400] Bad Request: user not found")) {
+ log.info("User {} not found in chat", userName);
+ SendMessage message = new SendMessage();
+ message.setChatId(userId);
+ message.setText("У вас не прав на создание алертов");
+ try {
+ execute(message);
+ throw new AlertTgBotException(String.format("User %s don't have permissions", userName), e);
+ } catch (TelegramApiException ex) {
+ throw new AlertTgBotException(
+ String.format("Error while checking user %s permissions", userName), e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java b/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java
new file mode 100644
index 0000000..1505849
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java
@@ -0,0 +1,39 @@
+package dev.vality.alert.tg.bot.service;
+
+import dev.vality.alerting.mayday.*;
+import lombok.RequiredArgsConstructor;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class MayDayService {
+
+ private final AlertingServiceSrv.Iface mayDayClient;
+
+ public void deleteAllAlerts(String userId) throws TException {
+ mayDayClient.deleteAllAlerts(userId);
+ }
+
+ public void deleteAlert(String userAlertId) throws TException {
+ mayDayClient.deleteAlert(userAlertId);
+ }
+
+ public List getUserAlerts(String userId) throws TException {
+ return mayDayClient.getUserAlerts(userId);
+ }
+
+ public List getSupportedAlerts() throws TException {
+ return mayDayClient.getSupportedAlerts();
+ }
+
+ public AlertConfiguration getAlertConfiguration(String alertId) throws TException {
+ return mayDayClient.getAlertConfiguration(alertId);
+ }
+
+ public void createAlert(CreateAlertRequest createAlertRequest) throws TException {
+ mayDayClient.createAlert(createAlertRequest);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java b/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java
new file mode 100644
index 0000000..74a9da4
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java
@@ -0,0 +1,44 @@
+package dev.vality.alert.tg.bot.service;
+
+
+import dev.vality.alert.tg.bot.exeptions.AlertTgBotException;
+import dev.vality.alerting.tg_bot.Notification;
+import dev.vality.alerting.tg_bot.NotifierServiceSrv;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.thrift.TException;
+import org.springframework.stereotype.Service;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class NotifierService implements NotifierServiceSrv.Iface {
+
+ private final AlertBot alertBot;
+
+ @Override
+ public void notify(Notification notification) {
+ log.info("Try to send notification {} to user {}", notification.getMessage(), notification.getReceiverId());
+ try {
+ if (alertBot.isChatMemberPermission(Long.valueOf(notification.getReceiverId()))) {
+ alertBot.execute(SendMessage.builder()
+ .chatId(Long.valueOf(notification.getReceiverId()))
+ .text(notification.getMessage()).build());
+
+ log.info("Notification {} to user {} was send",
+ notification.getMessage(),
+ notification.getReceiverId());
+ }
+ } catch (TelegramApiException | TException ex) {
+ throw new AlertTgBotException(
+ String.format(
+ "Received an exception while notify receiver %s with message %s",
+ notification.getReceiverId(),
+ notification.getMessage()),
+ ex);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java b/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java
new file mode 100644
index 0000000..a047683
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java
@@ -0,0 +1,29 @@
+package dev.vality.alert.tg.bot.servlet;
+
+import dev.vality.alerting.tg_bot.NotifierServiceSrv;
+import dev.vality.woody.thrift.impl.http.THServiceBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
+
+@WebServlet("/alert/tg/bot")
+public class AlertTgBotServlet extends GenericServlet {
+
+ @Autowired
+ private NotifierServiceSrv.Iface serverHandler;
+
+ private Servlet servlet;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+ servlet = new THServiceBuilder().build(NotifierServiceSrv.Iface.class, serverHandler);
+ }
+
+ @Override
+ public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+ servlet.service(request, response);
+ }
+}
diff --git a/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java b/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java
new file mode 100644
index 0000000..e033fd0
--- /dev/null
+++ b/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java
@@ -0,0 +1,39 @@
+package dev.vality.alert.tg.bot.utils;
+
+import dev.vality.alert.tg.bot.constants.MainMenu;
+import lombok.RequiredArgsConstructor;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RequiredArgsConstructor
+public class MainMenuBuilder {
+
+ public static InlineKeyboardMarkup buildMainInlineKeyboardMarkup() {
+ List> rowsInline = new ArrayList<>();
+ List rowInline = new ArrayList<>();
+ rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(MainMenu.CREATE_ALERT.getText())
+ .callbackData(MainMenu.CREATE_ALERT.getCallbackData())
+ .build()));
+ rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(MainMenu.GET_ALL_ALERTS.getText())
+ .callbackData(MainMenu.GET_ALL_ALERTS.getCallbackData())
+ .build()));
+ rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(MainMenu.DELETE_ALERT.getText())
+ .callbackData(MainMenu.DELETE_ALERT.getCallbackData())
+ .build()));
+ rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder()
+ .text(MainMenu.DELETE_ALL_ALERTS.getText())
+ .callbackData(MainMenu.DELETE_ALL_ALERTS.getCallbackData())
+ .build()));
+ rowsInline.add(rowInline);
+ InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup();
+ markupInline.setKeyboard(rowsInline);
+ return markupInline;
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..eab9f4e
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,50 @@
+server:
+ port: ${server.port}
+management:
+ security:
+ flag: false
+ server:
+ port: ${management.port}
+ metrics:
+ tags:
+ application: ${project.name}
+ export:
+ prometheus:
+ enabled: true
+ endpoint:
+ health:
+ show-details: always
+ metrics:
+ enabled: true
+ prometheus:
+ enabled: true
+ endpoints:
+ web:
+ exposure:
+ include: health,info,prometheus
+
+spring:
+ application:
+ name: '@project.name@'
+ output:
+ ansi:
+ enabled: always
+ main:
+ allow-bean-definition-overriding: true
+ datasource:
+ url: jdbc:postgresql://localhost:5432/alert_tg_bot
+ username: postgres
+ password: postgres
+
+info:
+ version: '@project.version@'
+ stage: dev
+
+mayday:
+ url: http://localhost:8022/mayday
+ networkTimeout: 5000
+
+bot:
+ token: test
+ name: AlertBot
+ chatId: test
diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql
new file mode 100644
index 0000000..cc9ad6e
--- /dev/null
+++ b/src/main/resources/db/migration/V1__init.sql
@@ -0,0 +1,26 @@
+CREATE SCHEMA IF NOT EXISTS alert_tg_bot;
+
+CREATE TABLE alert_tg_bot.state_data
+(
+ id BIGSERIAL NOT NULL,
+ user_id BIGINT NOT NULL,
+ alert_id CHARACTER VARYING,
+ map_params CHARACTER VARYING,
+
+ CONSTRAINT state_data_pkey PRIMARY KEY (id),
+ UNIQUE (user_id)
+);
+
+CREATE TYPE alert_tg_bot.parameter_type AS ENUM ('bl', 'fl', 'integer', 'str');
+
+CREATE TABLE alert_tg_bot.parameters_data
+(
+ id BIGSERIAL NOT NULL,
+ alert_id CHARACTER VARYING NOT NULL,
+ param_id CHARACTER VARYING NOT NULL,
+ param_name CHARACTER VARYING NOT NULL,
+ param_type alert_tg_bot.parameter_type NOT NULL,
+
+ CONSTRAINT parameters_pkey PRIMARY KEY (id),
+ UNIQUE (param_id)
+);
diff --git a/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java b/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java
new file mode 100644
index 0000000..7760166
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java
@@ -0,0 +1,141 @@
+package dev.vality.alert.tg.bot;
+
+import dev.vality.alert.tg.bot.domain.enums.ParameterType;
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import org.telegram.telegrambots.meta.api.objects.*;
+
+
+public abstract class TestObjectFactory {
+
+ public static StateData testStateData() {
+ StateData stateData = new StateData();
+ stateData.setUserId(122233L);
+ stateData.setAlertId("45");
+ stateData.setMapParams("{\"Процент\":34,\"Имя Терминала\":56}");
+ return stateData;
+ }
+
+ public static ParametersData testParameters() {
+ ParametersData params = new ParametersData();
+ params.setAlertId("32");
+ params.setParamName("Терминал");
+ params.setParamId("14");
+ params.setParamType(ParameterType.str);
+ return params;
+ }
+
+ public static Update testUpdateMessage() {
+ Chat chat = new Chat();
+ chat.setId(123L);
+ User user = new User();
+ user.setId(123L);
+ Message message = new Message();
+ message.setText("test");
+ message.setChat(chat);
+ message.setFrom(user);
+ Update update = new Update();
+ update.setMessage(message);
+ return update;
+ }
+
+ public static Update testUpdateMessageWithWithDifferentId() {
+ Chat chat = new Chat();
+ chat.setId(123L);
+ User user = new User();
+ user.setId(567L);
+ Message message = new Message();
+ message.setText("test");
+ message.setChat(chat);
+ message.setFrom(user);
+ Update update = new Update();
+ update.setMessage(message);
+ return update;
+ }
+
+ public static Update testUpdateReply() {
+ User user = new User();
+ user.setId(123L);
+ Chat chat = new Chat();
+ chat.setId(123L);
+ Message message = new Message();
+ message.setText("test");
+ message.setReplyToMessage(message);
+ message.setChat(chat);
+ message.setFrom(user);
+ Update update = new Update();
+ update.setMessage(message);
+ return update;
+ }
+
+ public static Update testUpdateReplyDeleteAlert() {
+ User user = new User();
+ user.setId(123L);
+ Chat chat = new Chat();
+ chat.setId(123L);
+ Message message = new Message();
+ message.setText("Введите id алерта для удаления");
+ message.setReplyToMessage(message);
+ message.setChat(chat);
+ message.setFrom(user);
+ Update update = new Update();
+ update.setMessage(message);
+ return update;
+ }
+
+ public static Update testUpdateDeleteAllCallback() {
+ User user = new User();
+ user.setId(123L);
+ CallbackQuery callbackQuery = new CallbackQuery();
+ callbackQuery.setFrom(user);
+ callbackQuery.setData("deleteAllAlerts");
+ Update update = new Update();
+ update.setCallbackQuery(callbackQuery);
+ return update;
+ }
+
+ public static Update testUpdateCreateAlertCallback() {
+ User user = new User();
+ user.setId(123L);
+ CallbackQuery callbackQuery = new CallbackQuery();
+ callbackQuery.setFrom(user);
+ callbackQuery.setData("createAlert");
+ Update update = new Update();
+ update.setCallbackQuery(callbackQuery);
+ return update;
+ }
+
+ public static Update testUpdateGetAllAlertsCallback() {
+ User user = new User();
+ user.setId(123L);
+ CallbackQuery callbackQuery = new CallbackQuery();
+ callbackQuery.setFrom(user);
+ callbackQuery.setData("getAllAlerts");
+ Update update = new Update();
+ update.setCallbackQuery(callbackQuery);
+ return update;
+ }
+
+ public static Update testUpdateReturnCallback() {
+ User user = new User();
+ user.setId(123L);
+ CallbackQuery callbackQuery = new CallbackQuery();
+ callbackQuery.setFrom(user);
+ callbackQuery.setData("return");
+ Update update = new Update();
+ update.setCallbackQuery(callbackQuery);
+ return update;
+ }
+
+ public static Update testUpdateMessageCallback() {
+ User user = new User();
+ user.setId(123L);
+ CallbackQuery callbackQuery = new CallbackQuery();
+ callbackQuery.setFrom(user);
+ callbackQuery.setData("test");
+ Update update = new Update();
+ update.setCallbackQuery(callbackQuery);
+ return update;
+ }
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java b/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java
new file mode 100644
index 0000000..ae4704e
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java
@@ -0,0 +1,19 @@
+package dev.vality.alert.tg.bot.config;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.sql.DataSource;
+
+@TestConfiguration
+@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
+ DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
+public class ExcludeDataSourceConfiguration {
+
+ @MockBean
+ private DataSource dataSource;
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java b/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java
new file mode 100644
index 0000000..2586f21
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java
@@ -0,0 +1,16 @@
+package dev.vality.alert.tg.bot.config;
+
+import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton;
+import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@PostgresqlTestcontainerSingleton
+@JooqTest
+public @interface PostgresqlJooqTest {
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java b/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java
new file mode 100644
index 0000000..f238279
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java
@@ -0,0 +1,57 @@
+package dev.vality.alert.tg.bot.dao;
+
+import dev.vality.alert.tg.bot.TestObjectFactory;
+import dev.vality.alert.tg.bot.config.PostgresqlJooqTest;
+import dev.vality.alert.tg.bot.dao.impl.ParametersDaoImpl;
+import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import static dev.vality.alert.tg.bot.domain.Tables.PARAMETERS_DATA;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@PostgresqlJooqTest
+@ContextConfiguration(classes = {ParametersDaoImpl.class})
+class ParametersDaoImplTest {
+
+ @Autowired
+ private ParametersDao parametersDao;
+
+ @Autowired
+ private DSLContext dslContext;
+
+ @BeforeEach
+ void setUp() {
+ dslContext.deleteFrom(PARAMETERS_DATA).execute();
+ }
+
+ @Test
+ void save() {
+ ParametersData parameters = TestObjectFactory.testParameters();
+
+ ParametersData savedParameters = parametersDao.save(parameters);
+
+ assertEquals(1, dslContext.fetchCount(PARAMETERS_DATA));
+ assertNotNull(savedParameters);
+ assertNotNull(savedParameters.getId());
+ assertEquals(parameters.getParamId(), savedParameters.getParamId());
+ }
+
+ @Test
+ void getByAlertIdAndParamName() {
+ ParametersData parameters = TestObjectFactory.testParameters();
+ dslContext.insertInto(PARAMETERS_DATA)
+ .set(dslContext.newRecord(PARAMETERS_DATA, parameters))
+ .execute();
+
+ ParametersData params =
+ parametersDao.getByAlertIdAndParamName(parameters.getAlertId(), parameters.getParamName());
+
+ assertEquals(parameters.getParamId(), params.getParamId());
+ }
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java b/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java
new file mode 100644
index 0000000..19ba789
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java
@@ -0,0 +1,73 @@
+package dev.vality.alert.tg.bot.dao;
+
+import dev.vality.alert.tg.bot.TestObjectFactory;
+import dev.vality.alert.tg.bot.config.PostgresqlJooqTest;
+import dev.vality.alert.tg.bot.dao.impl.StateDataDaoImpl;
+import dev.vality.alert.tg.bot.domain.tables.pojos.StateData;
+import dev.vality.alert.tg.bot.domain.tables.records.StateDataRecord;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import static dev.vality.alert.tg.bot.domain.Tables.STATE_DATA;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@PostgresqlJooqTest
+@ContextConfiguration(classes = {StateDataDaoImpl.class})
+class StateDaoImplTest {
+
+ @Autowired
+ private StateDataDao stateDataDao;
+
+ @Autowired
+ private DSLContext dslContext;
+
+ @BeforeEach
+ void setUp() {
+ dslContext.deleteFrom(STATE_DATA).execute();
+ }
+
+ @Test
+ void save() {
+ StateData stateData = TestObjectFactory.testStateData();
+
+ StateData savedStateData = stateDataDao.save(stateData);
+
+ assertEquals(1, dslContext.fetchCount(STATE_DATA));
+ assertNotNull(savedStateData);
+ assertNotNull(savedStateData.getId());
+ assertEquals(stateData.getAlertId(), savedStateData.getAlertId());
+ }
+
+ @Test
+ void updateParams() {
+ StateData stateData = TestObjectFactory.testStateData();
+ stateData.setMapParams("{\"Процент\":56,\"Имя Терминала\":test}");
+ dslContext.insertInto(STATE_DATA)
+ .set(dslContext.newRecord(STATE_DATA, stateData))
+ .execute();
+
+ String newMapParams = "{\"Процент\":56,\"Имя Терминала\":new}";
+ stateDataDao.updateParams(stateData.getUserId(), newMapParams);
+
+ StateDataRecord stateDataRecord = dslContext.fetchAny(STATE_DATA);
+
+ assertEquals(newMapParams, stateDataRecord.getMapParams());
+ }
+
+ @Test
+ void getByUserId() {
+ StateData stateData = TestObjectFactory.testStateData();
+ dslContext.insertInto(STATE_DATA)
+ .set(dslContext.newRecord(STATE_DATA, stateData))
+ .execute();
+
+ StateData data = stateDataDao.getByUserId(stateData.getUserId());
+
+ assertEquals(stateData.getUserId(), data.getUserId());
+ }
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java
new file mode 100644
index 0000000..e39c70a
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java
@@ -0,0 +1,105 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.mapper.JsonMapper;
+import dev.vality.alert.tg.bot.mapper.MenuCallbackMapper;
+import dev.vality.alert.tg.bot.mapper.ParametersCallbackMapper;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.AlertConfiguration;
+import dev.vality.alerting.mayday.ParameterConfiguration;
+import dev.vality.alerting.mayday.ParameterType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import java.util.Collections;
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.*;
+import static dev.vality.alert.tg.bot.constants.TextConstants.ALERTS_REMOVED;
+import static dev.vality.alert.tg.bot.constants.TextConstants.SELECT_ALERT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {CallbackHandler.class, MenuCallbackMapper.class, ParametersCallbackMapper.class,
+ JsonMapper.class})
+public class CallbackHandlerTest {
+ @MockBean
+ private ParametersDao parametersDao;
+ @MockBean
+ private StateDataDao stateDataDao;
+ @MockBean
+ private MayDayService mayDayService;
+ @Autowired
+ private CallbackHandler callbackHandler;
+
+
+ @Test
+ void testCallbackDeleteAllAlertsHandler() throws Exception {
+ Update update = testUpdateDeleteAllCallback();
+ SendMessage sendMessage = callbackHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertEquals(ALERTS_REMOVED.getText(), sendMessage.getText());
+ verify(mayDayService, times(1)).deleteAllAlerts(any());
+ }
+
+ @Test
+ void testCallbackCreateAlertHandler() throws Exception {
+ Update update = testUpdateCreateAlertCallback();
+ SendMessage sendMessage = callbackHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertEquals(SELECT_ALERT.getText(), sendMessage.getText());
+ verify(stateDataDao, times(1)).save(any());
+
+ }
+
+ @Test
+ void testCallbackGetAllAlertsHandler() throws Exception {
+ Update update = testUpdateGetAllAlertsCallback();
+ SendMessage sendMessage = callbackHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ verify(mayDayService, times(1)).getUserAlerts(any());
+
+ }
+
+ @Test
+ void testCallbackReturnHandler() throws Exception {
+ Update update = testUpdateReturnCallback();
+ SendMessage sendMessage = callbackHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ }
+
+ @Test
+ void testCallbackMessageHandler() throws Exception {
+ List parameterConfigurations =
+ Collections.singletonList(new ParameterConfiguration()
+ .setId("2").setName("test").setType(ParameterType.str));
+ when(mayDayService.getAlertConfiguration(any()))
+ .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations));
+ when(stateDataDao.getByUserId(any())).thenReturn(testStateData());
+ Update update = testUpdateMessageCallback();
+ SendMessage sendMessage = callbackHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ verify(mayDayService, times(1)).getAlertConfiguration(any());
+ verify(stateDataDao, times(1)).getByUserId(any());
+ verify(stateDataDao, times(1)).save(any());
+ verify(parametersDao, times(1)).save(any());
+
+ }
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java
new file mode 100644
index 0000000..04f5f45
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java
@@ -0,0 +1,41 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessage;
+import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessageWithWithDifferentId;
+import static dev.vality.alert.tg.bot.constants.TextConstants.REPLY_OR_ACTION;
+import static org.junit.jupiter.api.Assertions.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {MainMenuHandler.class})
+public class MainMenuHandlerTest {
+
+ @Autowired
+ private MainMenuHandler mainMenuHandler;
+
+
+ @Test
+ void testMainMenuWithWithDifferentIdHandle() {
+ Update update = testUpdateMessageWithWithDifferentId();
+ SendMessage sendMessage = mainMenuHandler.handle(update, 123L);
+ assertNull(sendMessage);
+ }
+
+ @Test
+ void testMainMenuHandle() {
+ Update update = testUpdateMessage();
+ SendMessage sendMessage = mainMenuHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertEquals(REPLY_OR_ACTION.getText(), sendMessage.getText());
+ }
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java
new file mode 100644
index 0000000..dd67fc8
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java
@@ -0,0 +1,44 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessage;
+import static dev.vality.alert.tg.bot.constants.TextConstants.SELECT_ACTION;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {MessageHandler.class})
+public class MessageHandlerTest {
+
+ @MockBean
+ private StateDataDao stateDataDao;
+ @Autowired
+ private MessageHandler messageHandler;
+
+
+ @Test
+ void testMessageHandle() {
+ Update update = testUpdateMessage();
+ SendMessage sendMessage = messageHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(SELECT_ACTION.getText(), sendMessage.getText());
+ verify(stateDataDao, times(1)).save(any());
+ }
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java
new file mode 100644
index 0000000..c683a6b
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java
@@ -0,0 +1,66 @@
+package dev.vality.alert.tg.bot.handler;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.mapper.JsonMapper;
+import dev.vality.alert.tg.bot.mapper.ReplyMessagesMapper;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.*;
+import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_CREATED;
+import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_REMOVED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {ReplyHandler.class, ReplyMessagesMapper.class, JsonMapper.class})
+public class ReplyHandlerTest {
+
+ @MockBean
+ private ParametersDao parametersDao;
+ @MockBean
+ private StateDataDao stateDataDao;
+ @MockBean
+ private MayDayService mayDayService;
+ @Autowired
+ private ReplyHandler replyHandler;
+
+
+ @Test
+ void testReplyHandle() throws Exception {
+ when(stateDataDao.getByUserId(anyLong())).thenReturn(testStateData());
+ when(parametersDao.getByAlertIdAndParamName(anyString(), anyString())).thenReturn(testParameters());
+ Update update = testUpdateReply();
+ SendMessage sendMessage = replyHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertEquals(ALERT_CREATED.getText(), sendMessage.getText());
+ verify(mayDayService, times(1)).createAlert(any());
+ verify(stateDataDao, times(2)).getByUserId(any());
+ verify(stateDataDao, times(1)).updateParams(any(), any());
+ }
+
+ @Test
+ void testReplyDeleteAlertHandle() throws Exception {
+ when(stateDataDao.getByUserId(anyLong())).thenReturn(testStateData());
+ when(parametersDao.getByAlertIdAndParamName(anyString(), anyString())).thenReturn(testParameters());
+ Update update = testUpdateReplyDeleteAlert();
+ SendMessage sendMessage = replyHandler.handle(update, 123L);
+ assertNotNull(sendMessage);
+ assertEquals(ALERT_REMOVED.getText(), sendMessage.getText());
+ verify(mayDayService, times(1)).deleteAlert(any());
+
+ }
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java
new file mode 100644
index 0000000..4bf03aa
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java
@@ -0,0 +1,90 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.UserAlert;
+import org.apache.thrift.TException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+
+import java.util.Collections;
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.constants.TextConstants.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {MenuCallbackMapper.class})
+public class MenuCallbackMapperTest {
+
+ @MockBean
+ private StateDataDao stateDataDao;
+ @MockBean
+ private MayDayService mayDayService;
+ @Autowired
+ private MenuCallbackMapper menuCallbackMapper;
+
+ @Test
+ void testGetAllAlertsCallback() throws Exception {
+ List userAlertsList = Collections.singletonList(new UserAlert().setId("2").setName("test"));
+ when(mayDayService.getUserAlerts(any()))
+ .thenReturn(userAlertsList);
+ SendMessage sendMessage = menuCallbackMapper.getAllAlertsCallback(123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ StringBuilder text = new StringBuilder("Ваши алерты:\n");
+ userAlertsList.forEach(userAlert -> {
+ text.append("id: ").append(userAlert.getId())
+ .append(" Название: ").append(userAlert.getName())
+ .append("\n");
+ });
+ assertEquals(text.toString(), sendMessage.getText());
+ }
+
+ @Test
+ void testDeleteAlertCallback() throws Exception {
+ SendMessage sendMessage = menuCallbackMapper.deleteAlertCallback(123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(ENTER_ALERT_ID_FOR_REMOVED.getText(), sendMessage.getText());
+ }
+
+ @Test
+ void testDeleteAllAlertsCallback() throws Exception {
+ SendMessage sendMessage = menuCallbackMapper.deleteAllAlertsCallback(123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(ALERTS_REMOVED.getText(), sendMessage.getText());
+ }
+
+ @Test
+ void testReturnCallback() {
+ SendMessage sendMessage = menuCallbackMapper.returnCallback();
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(SELECT_ACTION.getText(), sendMessage.getText());
+ }
+
+ @Test
+ void testCreateAlertCallback() throws TException {
+ SendMessage sendMessage = menuCallbackMapper.createAlertCallback(123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(SELECT_ALERT.getText(), sendMessage.getText());
+ verify(stateDataDao, times(1)).save(any());
+ verify(mayDayService, times(1)).getSupportedAlerts();
+ }
+
+
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java
new file mode 100644
index 0000000..6797b61
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java
@@ -0,0 +1,59 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.AlertConfiguration;
+import dev.vality.alerting.mayday.ParameterConfiguration;
+import dev.vality.alerting.mayday.ParameterType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+
+import java.util.Collections;
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.testStateData;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {ParametersCallbackMapper.class, JsonMapper.class})
+public class ParametersCallbackMapperTest {
+
+ @MockBean
+ private StateDataDao stateDataDao;
+ @MockBean
+ private ParametersDao parametersDao;
+ @MockBean
+ private MayDayService mayDayService;
+ @Autowired
+ private ParametersCallbackMapper parametersCallbackMapper;
+
+ @Test
+ void testMapParametersCallback() throws Exception {
+ List parameterConfigurations =
+ Collections.singletonList(new ParameterConfiguration()
+ .setId("2").setName("test").setType(ParameterType.str));
+ when(mayDayService.getAlertConfiguration(any()))
+ .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations));
+ when(stateDataDao.getByUserId(any())).thenReturn(testStateData());
+ SendMessage sendMessage = parametersCallbackMapper.mapParametersCallback("test", 123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals("test", sendMessage.getText());
+ verify(mayDayService, times(1)).getAlertConfiguration(any());
+ verify(stateDataDao, times(1)).getByUserId(any());
+ verify(stateDataDao, times(1)).save(any());
+ verify(parametersDao, times(1)).save(any());
+ }
+}
diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java
new file mode 100644
index 0000000..7b3b111
--- /dev/null
+++ b/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java
@@ -0,0 +1,78 @@
+package dev.vality.alert.tg.bot.mapper;
+
+import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration;
+import dev.vality.alert.tg.bot.dao.ParametersDao;
+import dev.vality.alert.tg.bot.dao.StateDataDao;
+import dev.vality.alert.tg.bot.service.MayDayService;
+import dev.vality.alerting.mayday.AlertConfiguration;
+import dev.vality.alerting.mayday.ParameterConfiguration;
+import dev.vality.alerting.mayday.ParameterType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+
+import java.util.Collections;
+import java.util.List;
+
+import static dev.vality.alert.tg.bot.TestObjectFactory.testParameters;
+import static dev.vality.alert.tg.bot.TestObjectFactory.testStateData;
+import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_CREATED;
+import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_REMOVED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@Import(ExcludeDataSourceConfiguration.class)
+@ContextConfiguration(classes = {ReplyMessagesMapper.class, JsonMapper.class})
+public class ReplyMessageMapperTest {
+
+ @MockBean
+ private StateDataDao stateDataDao;
+ @MockBean
+ private ParametersDao parametersDao;
+ @MockBean
+ private MayDayService mayDayService;
+ @Autowired
+ private ReplyMessagesMapper replyMessagesMapper;
+
+ @Test
+ void testCreateAlertRequest() throws Exception {
+ List parameterConfigurations =
+ Collections.singletonList(new ParameterConfiguration()
+ .setId("2").setName("test").setType(ParameterType.str));
+ when(mayDayService.getAlertConfiguration(any()))
+ .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations));
+ when(stateDataDao.getByUserId(any())).thenReturn(testStateData());
+ when(parametersDao.getByAlertIdAndParamName(any(), any())).thenReturn(testParameters());
+ SendMessage sendMessage = replyMessagesMapper.createAlertRequest(123L);
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(ALERT_CREATED.getText(), sendMessage.getText());
+ verify(mayDayService, times(1)).createAlert(any());
+ verify(stateDataDao, times(1)).getByUserId(any());
+ }
+
+ @Test
+ void testCreateNextParameterRequest() {
+ SendMessage sendMessage = replyMessagesMapper.createNextParameterRequest("test");
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals("test", sendMessage.getText());
+ }
+
+ @Test
+ void testDeleteAlert() throws Exception {
+ SendMessage sendMessage = replyMessagesMapper.deleteAlert("test");
+ assertNotNull(sendMessage);
+ assertNotNull(sendMessage.getReplyMarkup());
+ assertEquals(ALERT_REMOVED.getText(), sendMessage.getText());
+ verify(mayDayService, times(1)).deleteAlert(any());
+ }
+}
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..4bb5766
--- /dev/null
+++ b/src/test/resources/logback-test.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+