BUS-24: Initial commit (#1)

This commit is contained in:
malkoas 2023-06-13 16:00:46 +03:00 committed by GitHub
parent 0f8a1488e5
commit e35c7734a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2557 additions and 0 deletions

2
.github/settings.yml vendored Normal file
View File

@ -0,0 +1,2 @@
# These settings are synced to GitHub by https://probot.github.io/apps/settings/
_extends: .github

10
.github/workflows/basic-linters.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Vality basic linters
on:
pull_request:
branches:
- "*"
jobs:
lint:
uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1

10
.github/workflows/build.yml vendored Normal file
View File

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

13
.github/workflows/deploy.yml vendored Normal file
View File

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

78
.gitignore vendored Normal file
View File

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

176
LICENSE Normal file
View File

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

321
pom.xml Normal file
View File

@ -0,0 +1,321 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.vality</groupId>
<artifactId>service-parent-pom</artifactId>
<version>2.1.4</version>
</parent>
<artifactId>alert-tg-bot</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<server.port>8022</server.port>
<management.port>8023</management.port>
<exposed.ports>${server.port} ${management.port}</exposed.ports>
<db.name>alert_tg_bot</db.name>
<db.port>5432</db.port>
<db.url>jdbc:postgresql://localhost:${db.port}/${db.name}</db.url>
<db.user>postgres</db.user>
<db.password>postgres</db.password>
<db.schema>alert_tg_bot</db.schema>
<testcontainers.annotations.version>1.4.3</testcontainers.annotations.version>
</properties>
<dependencies>
<!--vality-->
<dependency>
<groupId>dev.vality</groupId>
<artifactId>mayday-proto</artifactId>
<version>1.5-5015827</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>alert-tg-bot-proto</artifactId>
<version>1.4-adf83ed</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>db-common-lib</artifactId>
</dependency>
<dependency>
<groupId>dev.vality.geck</groupId>
<artifactId>serializer</artifactId>
</dependency>
<dependency>
<groupId>dev.vality.geck</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>dev.vality.geck</groupId>
<artifactId>filter</artifactId>
</dependency>
<dependency>
<groupId>dev.vality.geck</groupId>
<artifactId>migrator</artifactId>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>adapter-common-lib</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>damsel</artifactId>
<version>1.597-bfedcb9</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>
<!--third party-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>testcontainers-annotations</artifactId>
<version>${testcontainers.annotations.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
<targetPath>${project.build.directory}</targetPath>
<includes>
<include>Dockerfile</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>Dockerfile</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<version>1.6.0</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-filtering</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
<configuration>
<resourceBundles>
<resourceBundle>dev.vality:shared-resources:${shared-resources.version}</resourceBundle>
</resourceBundles>
<attachToMain>false</attachToMain>
<attachToTest>false</attachToTest>
</configuration>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<url>${db.url}</url>
<user>${db.user}</user>
<password>${db.password}</password>
<schemas>
<schema>${db.schema}</schema>
</schemas>
<locations>
<location>filesystem:${project.basedir}/src/main/resources/db/migration</location>
</locations>
</configuration>
<executions>
<execution>
<id>migrate</id>
<phase>generate-sources</phase>
<goals>
<goal>migrate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<configuration>
<jdbc>
<driver>org.postgresql.Driver</driver>
<url>${db.url}</url>
<user>${db.user}</user>
<password>${db.password}</password>
</jdbc>
<generator>
<generate>
<javaTimeTypes>true</javaTimeTypes>
<pojos>true</pojos>
<pojosEqualsAndHashCode>true</pojosEqualsAndHashCode>
<pojosToString>true</pojosToString>
</generate>
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<includes>.*</includes>
<excludes>schema_version|flyway_schema_history</excludes>
<inputSchema>${db.schema}</inputSchema>
</database>
<target>
<packageName>dev.vality.alert.tg.bot.domain</packageName>
<directory>target/generated-sources/jooq</directory>
</target>
</generator>
</configuration>
<executions>
<execution>
<id>gen-src</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>dev.vality.maven.plugins</groupId>
<artifactId>pg-embedded-plugin</artifactId>
<version>1.0.1</version>
<configuration>
<port>${db.port}</port>
<dbName>${db.name}</dbName>
<schemas>
<schema>${db.schema}</schema>
</schemas>
</configuration>
<executions>
<execution>
<id>PG_server_start</id>
<phase>initialize</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>PG_server_stop</id>
<phase>compile</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

4
renovate.json Normal file
View File

@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["local>valitydev/.github:renovate-config"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> ALLOWED_USER_STATUSES =
Set.of(
CREATOR.getValue(),
ADMINISTRATOR.getValue(),
MEMBER.getValue()
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<StateData> 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<StateDataRecord> 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);
}
}

View File

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

View File

@ -0,0 +1,8 @@
package dev.vality.alert.tg.bot.exeptions;
public class UnknownHandlerException extends RuntimeException {
public UnknownHandlerException() {
super("Unknown handler");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String, String> 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<String, String> map) {
return map.entrySet().stream()
.filter(entry -> null == entry.getValue())
.findFirst().map(Map.Entry::getKey)
.orElse(null);
}
}

View File

@ -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<String, String> toMap(String json) {
return objectMapper.readValue(json, new TypeReference<>() {
});
}
}

View File

@ -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<Alert> alerts = mayDayService.getSupportedAlerts();
List<List<InlineKeyboardButton>> 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<InlineKeyboardButton> 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<UserAlert> 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;
}
}

View File

@ -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<ParameterConfiguration> 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<ParameterConfiguration> parameterConfigurations) {
StateData stateData = stateDataDao.getByUserId(userId);
stateData.setAlertId(alertId);
Map<String, String> 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;
};
}
}

View File

@ -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<String, String> paramMap = jsonMapper.toMap(stateData.getMapParams());
CreateAlertRequest createAlertRequest = new CreateAlertRequest();
createAlertRequest.setAlertId(stateData.getAlertId());
createAlertRequest.setUserId(String.valueOf(userId));
List<ParameterInfo> 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<String, String> 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)));
};
}
}

View File

@ -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<CommonHandler> handlers;
private final AlertBotProperties alertBotProperties;
private final MayDayService mayDayService;
public AlertBot(AlertBotProperties alertBotProperties,
List<CommonHandler> 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);
}
}
}
}

View File

@ -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<UserAlert> getUserAlerts(String userId) throws TException {
return mayDayClient.getUserAlerts(userId);
}
public List<Alert> 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);
}
}

View File

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

View File

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

View File

@ -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<List<InlineKeyboardButton>> rowsInline = new ArrayList<>();
List<InlineKeyboardButton> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<root level="warn">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="dev.vality.woody" level="ALL"/>
</configuration>