chore: first commit
commit
0e6b1e00f9
|
@ -0,0 +1,32 @@
|
||||||
|
.idea/
|
||||||
|
env-bot
|
||||||
|
src/gen/
|
||||||
|
env-bot
|
||||||
|
env-db
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
target/
|
|
@ -0,0 +1,17 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Execute all tests" type="JUnit" factoryName="JUnit">
|
||||||
|
<module name="mezzotre" />
|
||||||
|
<extension name="software.aws.toolkits.jetbrains.core.execution.JavaAwsConnectionExtension">
|
||||||
|
<option name="credential" />
|
||||||
|
<option name="region" />
|
||||||
|
<option name="useCurrentConnection" value="false" />
|
||||||
|
</extension>
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="package" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -0,0 +1,32 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Jooby run" type="MavenRunConfiguration" factoryName="Maven">
|
||||||
|
<MavenSettings>
|
||||||
|
<option name="myGeneralSettings" />
|
||||||
|
<option name="myRunnerSettings" />
|
||||||
|
<option name="myRunnerParameters">
|
||||||
|
<MavenRunnerParameters>
|
||||||
|
<option name="profiles">
|
||||||
|
<set />
|
||||||
|
</option>
|
||||||
|
<option name="goals">
|
||||||
|
<list>
|
||||||
|
<option value="jooby:run" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="pomFileName" />
|
||||||
|
<option name="profilesMap">
|
||||||
|
<map />
|
||||||
|
</option>
|
||||||
|
<option name="resolveToWorkspace" value="false" />
|
||||||
|
<option name="workingDirPath" value="$PROJECT_DIR$" />
|
||||||
|
</MavenRunnerParameters>
|
||||||
|
</option>
|
||||||
|
</MavenSettings>
|
||||||
|
<extension name="software.aws.toolkits.jetbrains.core.execution.JavaAwsConnectionExtension">
|
||||||
|
<option name="credential" />
|
||||||
|
<option name="region" />
|
||||||
|
<option name="useCurrentConnection" value="false" />
|
||||||
|
</extension>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="docker-compose up (debug)" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="composeProjectName" value="mezzotre" />
|
||||||
|
<option name="envFilePath" value="" />
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="DEBUG_OPTS" />
|
||||||
|
<option name="value" value="debug-" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="removeVolumesOnComposeDown" value="true" />
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -0,0 +1,18 @@
|
||||||
|
ARG DEBUG_BUILD
|
||||||
|
|
||||||
|
FROM maven:3.8-openjdk-17 as builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY . /build
|
||||||
|
|
||||||
|
RUN mvn package -B -DskipTests=true \
|
||||||
|
-Dmaven.test.skip=true \
|
||||||
|
-Dmaven.site.skip=true \
|
||||||
|
-Dmaven.javadoc.skip=true | grep -Ev '(Downloading|Downloaded)'
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/java17:${DEBUG_BUILD}nonroot
|
||||||
|
|
||||||
|
COPY --from=builder /build/target/mezzotre.jar /opt/mezzotre.jar
|
||||||
|
|
||||||
|
ENTRYPOINT ["java", "-jar", "/opt/mezzotre.jar"]
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Application configuration file. See https://github.com/typesafehub/config/blob/master/HOCON.md for more details
|
||||||
|
|
||||||
|
db.url = "jdbc:postgresql://localhost:5433/example"
|
||||||
|
db.user = example
|
||||||
|
db.password = example
|
||||||
|
telegram.key = akey
|
||||||
|
|
||||||
|
application.lang = en en-US it it-IT
|
||||||
|
|
||||||
|
server.port = 9191
|
||||||
|
server.gzip = true
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="15 seconds" debug="false">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.github.polpetta" level="TRACE" />
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -0,0 +1 @@
|
||||||
|
logback.xml
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="15 seconds" debug="false">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.github.polpetta" level="INFO" />
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,27 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
- DEBUG_BUILD=${DEBUG_OPTS}
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- db.url=jdbc:postgresql://db:5432/mezzotre
|
||||||
|
- db.user=mezzotre
|
||||||
|
- db.password=${DB_PASSWORD}
|
||||||
|
- telegram.key=${TELEGRAM_KEY}
|
||||||
|
- application.env=${APPLICATION_MODE}
|
||||||
|
ports:
|
||||||
|
- "9191:9191"
|
||||||
|
volumes:
|
||||||
|
- ./conf/application.conf:/home/nonroot/application.${APPLICATION_MODE}.conf:ro
|
||||||
|
- ./conf/logback.${APPLICATION_MODE}.xml:/home/nonroot/logback.${APPLICATION_MODE}.xml:ro
|
||||||
|
db:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
|
- POSTGRES_USER=mezzotre
|
||||||
|
- POSTGRES_DB=mezzotre
|
|
@ -0,0 +1,363 @@
|
||||||
|
<?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/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>mezzotre</artifactId>
|
||||||
|
<groupId>com.github.polpetta</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Mezzotre</name>
|
||||||
|
<description>A simple Telegram assistant for DnD content</description>
|
||||||
|
<inceptionYear>2023</inceptionYear>
|
||||||
|
<contributors>
|
||||||
|
<contributor>
|
||||||
|
<name>Davide Polonio</name>
|
||||||
|
<url>https://bitdispenser.dev</url>
|
||||||
|
<email>davide+mezzotre@poldebra.me</email>
|
||||||
|
</contributor>
|
||||||
|
</contributors>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- Startup class -->
|
||||||
|
<application.class>com.github.polpetta.mezzotre.App</application.class>
|
||||||
|
|
||||||
|
<jooby.version>2.16.2</jooby.version>
|
||||||
|
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<maven.compiler.parameters>true</maven.compiler.parameters>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<javax.validation.version>2.0.1.Final</javax.validation.version>
|
||||||
|
<!-- Cannot upgrade more since we need Jooby 3-->
|
||||||
|
<ebean.version>12.16.0</ebean.version>
|
||||||
|
<org.mockito.mockio-core.version>4.3.1</org.mockito.mockio-core.version>
|
||||||
|
<mock-server.version>5.11.2</mock-server.version>
|
||||||
|
<testcontainers-postgresql.version>1.16.3</testcontainers-postgresql.version>
|
||||||
|
<org.testcontainers.junit-jupiter.version>1.16.3</org.testcontainers.junit-jupiter.version>
|
||||||
|
<jsonschema2pojo.version>1.1.1</jsonschema2pojo.version>
|
||||||
|
<jackson-databind.version>2.13.3</jackson-databind.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Server -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-netty</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-test</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-guice</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
<version>2.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Database dependencies-->
|
||||||
|
<!-- DataSource via HikariCP-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-hikari</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Ebean Module-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-ebean</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.ebean</groupId>
|
||||||
|
<artifactId>ebean-test</artifactId>
|
||||||
|
<version>${ebean.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.5.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
<version>${javax.validation.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flyway Module-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-flyway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jackson-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-gson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Starting banner-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-banner</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<version>${org.testcontainers.junit-jupiter.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${testcontainers-postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${org.testcontainers.junit-jupiter.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mock-server</groupId>
|
||||||
|
<artifactId>mockserver-netty</artifactId>
|
||||||
|
<version>${mock-server.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mock-server</groupId>
|
||||||
|
<artifactId>mockserver-junit-jupiter</artifactId>
|
||||||
|
<version>${mock-server.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.velocity</groupId>
|
||||||
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.velocity.tools/velocity-tools-generic -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.velocity.tools</groupId>
|
||||||
|
<artifactId>velocity-tools-generic</artifactId>
|
||||||
|
<version>3.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--Telegram stuff-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.pengrad</groupId>
|
||||||
|
<artifactId>java-telegram-bot-api</artifactId>
|
||||||
|
<version>6.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/io.vavr/vavr -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vavr</groupId>
|
||||||
|
<artifactId>vavr</artifactId>
|
||||||
|
<version>1.0.0-alpha-4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson-databind.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>31.1-jre</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>mezzotre</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jsonschema2pojo</groupId>
|
||||||
|
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
|
||||||
|
<version>${jsonschema2pojo.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirectory>${basedir}/schema/json</sourceDirectory>
|
||||||
|
<targetPackage>com.github.polpetta.types.json</targetPackage>
|
||||||
|
<outputDirectory>${basedir}/src/gen/java</outputDirectory>
|
||||||
|
<sourceType>jsonschema</sourceType>
|
||||||
|
<usePrimitives>true</usePrimitives>
|
||||||
|
<generateBuilders>true</generateBuilders>
|
||||||
|
<useInnerClassBuilders>true</useInnerClassBuilders>
|
||||||
|
<useLongIntegers>true</useLongIntegers>
|
||||||
|
<useDoubleNumbers>true</useDoubleNumbers>
|
||||||
|
<includeHashcodeAndEquals>true</includeHashcodeAndEquals>
|
||||||
|
<includeToString>true</includeToString>
|
||||||
|
<annotationStyle>jackson2</annotationStyle>
|
||||||
|
<includeJsr303Annotations>true</includeJsr303Annotations>
|
||||||
|
<includeJsr305Annotations>true</includeJsr305Annotations>
|
||||||
|
<useOptionalForGetters>true</useOptionalForGetters>
|
||||||
|
<removeOldOutput>true</removeOldOutput>
|
||||||
|
<initializeCollections>true</initializeCollections>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>generate</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.6.2</version>
|
||||||
|
<configuration>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-parameters</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-apt</artifactId>
|
||||||
|
<version>2.16.2</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>io.ebean</groupId>
|
||||||
|
<artifactId>querybean-generator</artifactId>
|
||||||
|
<version>${ebean.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-maven-plugin</artifactId>
|
||||||
|
<version>2.16.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>openapi</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- Build uber jar-->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>uber-jar</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<minimizeJar>false</minimizeJar>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>${application.class}</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.repaint.maven</groupId>
|
||||||
|
<artifactId>tiles-maven-plugin</artifactId>
|
||||||
|
<version>2.24</version>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<configuration>
|
||||||
|
<tiles>
|
||||||
|
<tile>io.jooby:jooby-stork:2.16.2</tile>
|
||||||
|
<tile>io.ebean.tile:enhancement:${ebean.version}</tile>
|
||||||
|
</tiles>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||||
|
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-bom</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||||
|
"$id": "http://example.com/example.json",
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"required": [
|
||||||
|
"stage",
|
||||||
|
"step",
|
||||||
|
"previousMessageUnixTimestampInSeconds"
|
||||||
|
],
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"stage": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"previousMessageUnixTimestampInSeconds": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"lastMessageSentId": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Name of application (make sure it has no spaces)
|
||||||
|
name: "${project.artifactId}"
|
||||||
|
|
||||||
|
# Display name of application (can have spaces)
|
||||||
|
display_name: "${project.name}"
|
||||||
|
|
||||||
|
# Type of launcher (CONSOLE or DAEMON)
|
||||||
|
type: DAEMON
|
||||||
|
|
||||||
|
# Java class to run
|
||||||
|
main_class: "${application.class}"
|
||||||
|
|
||||||
|
domain: "${project.groupId}"
|
||||||
|
|
||||||
|
short_description: "${project.artifactId}"
|
||||||
|
|
||||||
|
# Platform launchers to generate (WINDOWS, LINUX, MAC_OSX)
|
||||||
|
# Linux launcher is suitable for Bourne shells (e.g. Linux/BSD)
|
||||||
|
platforms: [ LINUX ]
|
||||||
|
|
||||||
|
# Working directory for app
|
||||||
|
# RETAIN will not change the working directory
|
||||||
|
# APP_HOME will change the working directory to the home of the app
|
||||||
|
# (where it was intalled) before running the main class
|
||||||
|
working_dir_mode: RETAIN
|
||||||
|
|
||||||
|
# Minimum version of java required (system will be searched for acceptable jvm)
|
||||||
|
min_java_version: "17"
|
||||||
|
|
||||||
|
# Min/max fixed memory (measured in MB)
|
||||||
|
min_java_memory: 512
|
||||||
|
max_java_memory: 512
|
||||||
|
|
||||||
|
# Min/max memory by percentage of system
|
||||||
|
#min_java_memory_pct: 10
|
||||||
|
#max_java_memory_pct: 20
|
||||||
|
|
||||||
|
# Try to create a symbolic link to java executable in <app_home>/run with
|
||||||
|
# the name of "<app_name>-java" so that commands like "ps" will make it
|
||||||
|
# easier to find your app
|
||||||
|
symlink_java: true
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.github.polpetta.mezzotre;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.orm.di.Db;
|
||||||
|
import com.github.polpetta.mezzotre.route.Telegram;
|
||||||
|
import com.github.polpetta.mezzotre.route.di.Route;
|
||||||
|
import com.github.polpetta.mezzotre.telegram.command.di.Command;
|
||||||
|
import com.github.polpetta.mezzotre.util.di.ThreadPool;
|
||||||
|
import com.google.inject.*;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
import io.jooby.*;
|
||||||
|
import io.jooby.banner.BannerModule;
|
||||||
|
import io.jooby.di.GuiceModule;
|
||||||
|
import io.jooby.di.JoobyModule;
|
||||||
|
import io.jooby.ebean.TransactionalRequest;
|
||||||
|
import io.jooby.json.GsonModule;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class App extends Jooby {
|
||||||
|
|
||||||
|
public static final Function<Jooby, Collection<Module>> DEFAULT_DI_MODULES =
|
||||||
|
(jooby) -> {
|
||||||
|
final HashSet<Module> modules = new HashSet<>();
|
||||||
|
modules.add(new Db());
|
||||||
|
modules.add(new ThreadPool());
|
||||||
|
modules.add(new Route());
|
||||||
|
modules.add(new Command());
|
||||||
|
return modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
public App() {
|
||||||
|
this(Stage.PRODUCTION, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public App(Stage runningEnv, Collection<Module> modules) {
|
||||||
|
// FIXME change it in some configuration file!
|
||||||
|
setName("Mezzotre");
|
||||||
|
setVersion("1.0-SNAPSHOT");
|
||||||
|
// END FIXME
|
||||||
|
|
||||||
|
Collection<Module> toInject =
|
||||||
|
new ArrayList<>(Optional.ofNullable(modules).orElse(Collections.emptySet()));
|
||||||
|
if (modules == null || modules.size() == 0) {
|
||||||
|
toInject = DEFAULT_DI_MODULES.apply(this);
|
||||||
|
}
|
||||||
|
toInject.add(new InjectionModule(this));
|
||||||
|
toInject.add(new JoobyModule(this));
|
||||||
|
|
||||||
|
final Injector injector = Guice.createInjector(runningEnv, toInject);
|
||||||
|
install(new GuiceModule(injector)); // We need to do it here
|
||||||
|
|
||||||
|
install(new BannerModule("Mezzotre"));
|
||||||
|
install(new OpenAPIModule());
|
||||||
|
install(new GsonModule());
|
||||||
|
install(injector.getInstance(Key.get(Extension.class, Names.named("hikariPoolExtension"))));
|
||||||
|
install(
|
||||||
|
injector.getInstance(Key.get(Extension.class, Names.named("flyWayMigrationExtension"))));
|
||||||
|
install(injector.getInstance(Key.get(Extension.class, Names.named("ebeanExtension"))));
|
||||||
|
|
||||||
|
decorator(new AccessLogHandler());
|
||||||
|
decorator(new TransactionalRequest());
|
||||||
|
|
||||||
|
// We need to put this there otherwise MockRouter for testing purposes won't work
|
||||||
|
get("/", ctx -> "Hello World!");
|
||||||
|
mvc(Telegram.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
runApp(args, ExecutionMode.EVENT_LOOP, App::new);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.github.polpetta.mezzotre;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Provides;import io.jooby.Jooby;import org.slf4j.Logger;
|
||||||
|
|
||||||
|
public class InjectionModule extends AbstractModule {
|
||||||
|
private final Jooby jooby;
|
||||||
|
|
||||||
|
public InjectionModule(Jooby jooby) {
|
||||||
|
this.jooby = jooby;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public Logger getLogger() {
|
||||||
|
return jooby.getLog();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.github.polpetta.mezzotre.i18n;
|
||||||
|
|
||||||
|
import org.apache.velocity.app.VelocityEngine;
|
||||||
|
import org.apache.velocity.runtime.resource.loader.JarResourceLoader;
|
||||||
|
import org.apache.velocity.tools.Scope;
|
||||||
|
import org.apache.velocity.tools.ToolContext;
|
||||||
|
import org.apache.velocity.tools.ToolManager;
|
||||||
|
import org.apache.velocity.tools.config.FactoryConfiguration;
|
||||||
|
import org.apache.velocity.tools.config.ToolConfiguration;
|
||||||
|
import org.apache.velocity.tools.config.ToolboxConfiguration;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class LocalizedMessageFactory {
|
||||||
|
|
||||||
|
private final VelocityEngine velocityEngine;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LocalizedMessageFactory(VelocityEngine velocityEngine) {
|
||||||
|
this.velocityEngine = velocityEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolManager create(Locale locale) {
|
||||||
|
|
||||||
|
// properties.setProperty("file.resource.loader.class", FileResourceLoader.class.getName());.
|
||||||
|
|
||||||
|
|
||||||
|
final ToolManager toolManager = new ToolManager();
|
||||||
|
toolManager.setVelocityEngine(velocityEngine);
|
||||||
|
final FactoryConfiguration factoryConfiguration = new FactoryConfiguration();
|
||||||
|
final ToolboxConfiguration toolboxConfiguration = new ToolboxConfiguration();
|
||||||
|
toolboxConfiguration.setScope(Scope.REQUEST);
|
||||||
|
final ToolConfiguration toolConfiguration = new ToolConfiguration();
|
||||||
|
toolConfiguration.setClassname(LocalizedTool.class.getName());
|
||||||
|
toolConfiguration.setProperty("file.resource.loader.class", JarResourceLoader.class.getName());
|
||||||
|
toolConfiguration.setProperty(ToolContext.LOCALE_KEY, locale);
|
||||||
|
toolConfiguration.setProperty(LocalizedTool.BUNDLES_KEY, "i18n/message");
|
||||||
|
toolboxConfiguration.addTool(toolConfiguration);
|
||||||
|
factoryConfiguration.addToolbox(toolboxConfiguration);
|
||||||
|
toolManager.configure(factoryConfiguration);
|
||||||
|
return toolManager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.github.polpetta.mezzotre.i18n;
|
||||||
|
|
||||||
|
import org.apache.velocity.tools.config.DefaultKey;
|
||||||
|
import org.apache.velocity.tools.generic.ResourceTool;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@DefaultKey("i18n")
|
||||||
|
public class LocalizedTool extends ResourceTool {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLocale(Locale locale) {
|
||||||
|
super.setLocale(locale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.di;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;import com.google.inject.Provides;import com.google.inject.Singleton;import com.zaxxer.hikari.HikariConfig;import io.jooby.Extension;import io.jooby.ebean.EbeanModule;import io.jooby.flyway.FlywayModule;import io.jooby.hikari.HikariModule;import javax.inject.Named;
|
||||||
|
|
||||||
|
public class Db extends AbstractModule {
|
||||||
|
/**
|
||||||
|
* Returns null. This allows to fetch the configuration from file rather than fetch from other
|
||||||
|
* environment
|
||||||
|
*
|
||||||
|
* @return a {@link HikariConfig} configuration if possible
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public HikariConfig getHikariConfig() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("hikariPoolExtension")
|
||||||
|
public Extension getHikariExtension() {
|
||||||
|
return new HikariModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("flyWayMigrationExtension")
|
||||||
|
public Extension getFlyWayMigrationExtension() {
|
||||||
|
return new FlywayModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("ebeanExtension")
|
||||||
|
public Extension getEbeanExtension() {
|
||||||
|
return new EbeanModule();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.model;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import io.ebean.annotation.WhenCreated;
|
||||||
|
import io.ebean.annotation.WhenModified;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Father class to all the database objects. It allows to add when an object has been created and modifies
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
@MappedSuperclass
|
||||||
|
public class Base extends Model {
|
||||||
|
|
||||||
|
@WhenCreated private Instant entryCreated;
|
||||||
|
|
||||||
|
@WhenModified private Instant entryModified;
|
||||||
|
|
||||||
|
public Instant getEntryModified() {
|
||||||
|
return entryModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getEntryCreated() {
|
||||||
|
return entryCreated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.model;
|
||||||
|
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import io.ebean.annotation.DbJsonB;
|
||||||
|
import io.ebean.annotation.Length;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TgChat represents a chat with a possible Telegram user. Here information about the chat settings,
|
||||||
|
* such as the locale to use and the {@link ChatContext} are stored
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "telegram_chat")
|
||||||
|
public class TgChat extends Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the conversation. For chat with users, the rules of thumb is that this id is
|
||||||
|
* positive, while if it is a group or supergroup it is negative
|
||||||
|
*/
|
||||||
|
@Id private final Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This element in the database indicates which step the user is with the bot interaction. It can
|
||||||
|
* be useful to track and store chat metadata and also have a context about the conversation
|
||||||
|
* itself. Since the metadata stored can vary there's not a solid structured. Please use {@link
|
||||||
|
* ChatContext#getAdditionalProperties()} to retrieve for properties that are not statically
|
||||||
|
* typed.
|
||||||
|
*/
|
||||||
|
@DbJsonB
|
||||||
|
@Column(columnDefinition = "jsonb not null default '{}'::jsonb")
|
||||||
|
@NotNull
|
||||||
|
private ChatContext chatContext;
|
||||||
|
|
||||||
|
/** The locale to use when chatting. Defaults to {@code en-US} */
|
||||||
|
@NotNull
|
||||||
|
@Length(5)
|
||||||
|
@Column(columnDefinition = "varchar(5) not null default 'en-US'")
|
||||||
|
private String locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #TgChat( Long, ChatContext, String)}. The default locale set here is {@code en-US}
|
||||||
|
*/
|
||||||
|
public TgChat(Long id, ChatContext chatContext) {
|
||||||
|
this.id = id;
|
||||||
|
this.chatContext = chatContext;
|
||||||
|
this.locale = "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a new Telegram Chat
|
||||||
|
*
|
||||||
|
* @param id the id of the chat (can be negative)
|
||||||
|
* @param chatContext the context of the chat, where possible metadata can be stored. See {@link
|
||||||
|
* ChatContext}
|
||||||
|
* @param locale a specific locale for this chat
|
||||||
|
*/
|
||||||
|
public TgChat(Long id, @Nullable ChatContext chatContext, String locale) {
|
||||||
|
this.id = id;
|
||||||
|
this.chatContext = chatContext;
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public ChatContext getChatContext() {
|
||||||
|
return chatContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChatContext(@NotNull ChatContext chatContext) {
|
||||||
|
this.chatContext = chatContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocale(String locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TgChat{"
|
||||||
|
+ "id="
|
||||||
|
+ id
|
||||||
|
+ ", chatContext="
|
||||||
|
+ chatContext
|
||||||
|
+ ", locale='"
|
||||||
|
+ locale
|
||||||
|
+ '\''
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.model;
|
||||||
|
|
||||||
|
import io.ebean.annotation.ConstraintMode;
|
||||||
|
import io.ebean.annotation.DbForeignKey;
|
||||||
|
import io.ebean.annotation.Length;
|
||||||
|
import io.ebean.annotation.NotNull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "registered_user")
|
||||||
|
public class User extends Base {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Length(64)
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "boolean default false")
|
||||||
|
@NotNull
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@OneToOne(fetch = FetchType.LAZY, optional = true)
|
||||||
|
@DbForeignKey(onDelete = ConstraintMode.CASCADE)
|
||||||
|
@JoinColumn(name = "telegram_id", referencedColumnName = "id")
|
||||||
|
private TgChat telegramId;
|
||||||
|
|
||||||
|
@Length(256)
|
||||||
|
@Nullable
|
||||||
|
private String emailAddress;
|
||||||
|
|
||||||
|
public User(String id, String emailAddress, Boolean isActive, TgChat telegramId) {
|
||||||
|
this.id = id;
|
||||||
|
this.emailAddress = emailAddress;
|
||||||
|
this.isActive = isActive;
|
||||||
|
this.telegramId = telegramId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isActive() {
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Boolean active) {
|
||||||
|
isActive = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TgChat getTelegramId() {
|
||||||
|
return telegramId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTelegramId(TgChat telegramId) {
|
||||||
|
this.telegramId = telegramId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailAddress() {
|
||||||
|
return emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailAddress(String emailAddress) {
|
||||||
|
this.emailAddress = emailAddress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.github.polpetta.mezzotre.route;
|
||||||
|
|
||||||
|
public final class Constants {
|
||||||
|
public final static String V1 = "v1";
|
||||||
|
public final static String API = "api";
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.github.polpetta.mezzotre.route;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
||||||
|
import com.github.polpetta.mezzotre.telegram.command.Router;
|
||||||
|
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.pengrad.telegrambot.TelegramBot;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import io.jooby.Context;
|
||||||
|
import io.jooby.MediaType;
|
||||||
|
import io.jooby.annotations.POST;
|
||||||
|
import io.jooby.annotations.Path;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
@Tag(name = "Telegram", description = "Telegram webhook endpoint")
|
||||||
|
@Path("/" + Constants.API + "/" + Telegram.ENDPOINT)
|
||||||
|
public class Telegram {
|
||||||
|
|
||||||
|
static final String ENDPOINT = "tg";
|
||||||
|
private final Logger log;
|
||||||
|
private final TelegramBot bot;
|
||||||
|
private final Gson gson;
|
||||||
|
private final Executor completableFutureThreadPool;
|
||||||
|
private final UUIDGenerator uuidGenerator;
|
||||||
|
private final Router router;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Telegram(
|
||||||
|
Logger log,
|
||||||
|
TelegramBot bot,
|
||||||
|
Gson gson,
|
||||||
|
@Named("eventThreadPool") Executor completableFutureThreadPool,
|
||||||
|
UUIDGenerator uuidGenerator,
|
||||||
|
Router router) {
|
||||||
|
this.log = log;
|
||||||
|
this.bot = bot;
|
||||||
|
this.gson = gson;
|
||||||
|
this.completableFutureThreadPool = completableFutureThreadPool;
|
||||||
|
this.uuidGenerator = uuidGenerator;
|
||||||
|
this.router = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Telegram webhook entrypoint",
|
||||||
|
description =
|
||||||
|
"Telegram servers will call this endpoint to send updates about incoming messages",
|
||||||
|
tags = "telegram",
|
||||||
|
requestBody = @RequestBody(required = true))
|
||||||
|
@POST
|
||||||
|
public CompletableFuture<String> incomingUpdate(Context context, Update update) {
|
||||||
|
return CompletableFuture.supplyAsync(
|
||||||
|
() -> {
|
||||||
|
context.setResponseType(MediaType.JSON);
|
||||||
|
log.trace(gson.toJson(update));
|
||||||
|
|
||||||
|
final Message message = update.message();
|
||||||
|
return new QTgChat()
|
||||||
|
.id
|
||||||
|
.eq(message.chat().id())
|
||||||
|
.findOneOrEmpty()
|
||||||
|
.map(
|
||||||
|
u -> {
|
||||||
|
log.debug(
|
||||||
|
"Telegram chat " + u.getId() + " already registered in the database");
|
||||||
|
return u;
|
||||||
|
})
|
||||||
|
.orElseGet(
|
||||||
|
() -> {
|
||||||
|
final TgChat newTgChat = new TgChat(message.chat().id(), new ChatContext());
|
||||||
|
newTgChat.save();
|
||||||
|
log.trace(
|
||||||
|
"New Telegram chat " + newTgChat.getId() + " added into the database");
|
||||||
|
return newTgChat;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
completableFutureThreadPool)
|
||||||
|
.thenComposeAsync(tgChat -> router.process(tgChat, update), completableFutureThreadPool)
|
||||||
|
// See https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates
|
||||||
|
.thenApply(
|
||||||
|
tgResponse -> {
|
||||||
|
final String response = tgResponse.map(BaseRequest::toWebhookResponse).orElse("{}");
|
||||||
|
log.trace("About to send back " + response);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.github.polpetta.mezzotre.route.di;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import com.pengrad.telegrambot.TelegramBot;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
public class Route extends AbstractModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public TelegramBot getTelegramBot(Jooby jooby) {
|
||||||
|
final String telegramKey =
|
||||||
|
Optional.ofNullable(jooby.getEnvironment().getConfig().getString("telegram.key"))
|
||||||
|
.or(
|
||||||
|
() ->
|
||||||
|
Optional.ofNullable(
|
||||||
|
jooby.getEnvironment().getConfig().getString("TELEGRAM_KEY")))
|
||||||
|
.filter(s -> !s.isBlank())
|
||||||
|
.orElseThrow(
|
||||||
|
() ->
|
||||||
|
new IllegalStateException(
|
||||||
|
"Telegram token is required to make the application work. Please set 'telegram.key = \"value\"' in your application.conf file or as the application argument. Alternatively, you can set 'TELEGRAM_KEY' as an environment variable."));
|
||||||
|
return new TelegramBot(telegramKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
public class CommandNotFoundException extends RuntimeException {
|
||||||
|
public CommandNotFoundException() {}
|
||||||
|
|
||||||
|
public CommandNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandNotFoundException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandNotFoundException(
|
||||||
|
String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interfaces provides a way to digest incoming updates that are commands, e.g. {@code
|
||||||
|
* /this_command arg1}
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public interface Executor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the keyword to trigger this executor. Note that it must start with "/" at the
|
||||||
|
* beginning, e.g. {@code /start}.
|
||||||
|
*
|
||||||
|
* @return a {@link String} providing the keyword to trigger the current {@link Executor}
|
||||||
|
*/
|
||||||
|
String getTriggerKeyword();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the current update
|
||||||
|
*
|
||||||
|
* @param chat the chat the {@link Executor} is currently replying to
|
||||||
|
* @param update the update to process
|
||||||
|
* @return a {@link CompletableFuture} with the result of the computation
|
||||||
|
*/
|
||||||
|
// FIXME cannot be void - we don't want pesky side effects!
|
||||||
|
CompletableFuture<Optional<BaseRequest<?,?>>> process(TgChat chat, Update update);
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class has the goal of dispatching incoming {@link Update} events to the right {@link
|
||||||
|
* Executor}, that will provide an adequate response.
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class Router {
|
||||||
|
|
||||||
|
private final Set<Executor> tgExecutors;
|
||||||
|
private final java.util.concurrent.Executor threadPool;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Router(
|
||||||
|
@Named("commands") Set<Executor> tgExecutors,
|
||||||
|
@Named("eventThreadPool") java.util.concurrent.Executor threadPool) {
|
||||||
|
this.tgExecutors = tgExecutors;
|
||||||
|
this.threadPool = threadPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the incoming {@link Update}. If no suitable {@link Executor} is able to process the
|
||||||
|
* command, then {@link CommandNotFoundException} is used to signal this event.
|
||||||
|
*
|
||||||
|
* @param update the update coming from Telegram Servers
|
||||||
|
* @return a {@link CompletableFuture} that is marked as failure and containing a {@link
|
||||||
|
* CommandNotFoundException} exception if no suitable {@link Executor} is found
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||||
|
// This way exceptions are always under control
|
||||||
|
return CompletableFuture.completedStage(update)
|
||||||
|
.toCompletableFuture()
|
||||||
|
.thenComposeAsync(
|
||||||
|
up ->
|
||||||
|
/*
|
||||||
|
Brief explanation of this chain:
|
||||||
|
1 - Check if the message has a command in it (e.g. "/start hey!")
|
||||||
|
1.a - If it has, then search for the proper executor and go with it
|
||||||
|
2 - If there is no command (e.g "example") then go to the chat context and retrieve the stage we
|
||||||
|
could be in (maybe we're continuing a chat from previous messages?)
|
||||||
|
2.a - If there's a context with a valid stage, then continue with it
|
||||||
|
*/
|
||||||
|
Optional.of(up.message().text().split(" "))
|
||||||
|
.filter(list -> list.length > 0)
|
||||||
|
.map(list -> list[0])
|
||||||
|
.filter(wannabeCommand -> wannabeCommand.startsWith("/"))
|
||||||
|
.or(() -> Optional.ofNullable(chat.getChatContext().getStage()))
|
||||||
|
.flatMap(
|
||||||
|
command ->
|
||||||
|
tgExecutors.stream()
|
||||||
|
.filter(ex -> ex.getTriggerKeyword().equals(command))
|
||||||
|
.findAny())
|
||||||
|
.map(executor -> executor.process(chat, up))
|
||||||
|
.orElse(CompletableFuture.failedFuture(new CommandNotFoundException())),
|
||||||
|
threadPool);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.mezzotre.util.Clock;import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import io.vavr.control.Try;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.apache.velocity.VelocityContext;
|
||||||
|
import org.apache.velocity.tools.ToolManager;
|
||||||
|
import org.apache.velocity.util.StringBuilderWriter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This {@link Executor} has the goal to greet a user that typed {@code /start} to the bot.
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class Start implements Executor {
|
||||||
|
|
||||||
|
private final java.util.concurrent.Executor threadPool;
|
||||||
|
private final Logger log;
|
||||||
|
private final LocalizedMessageFactory localizedMessageFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Start(
|
||||||
|
LocalizedMessageFactory localizedMessageFactory,
|
||||||
|
@Named("eventThreadPool") java.util.concurrent.Executor threadPool,
|
||||||
|
Logger log) {
|
||||||
|
this.localizedMessageFactory = localizedMessageFactory;
|
||||||
|
this.threadPool = threadPool;
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTriggerKeyword() {
|
||||||
|
return "/start";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||||
|
return CompletableFuture.supplyAsync(
|
||||||
|
() -> {
|
||||||
|
/*
|
||||||
|
Steps:
|
||||||
|
1 - Load the template and generate the message with the defined locale
|
||||||
|
2 - Update the chat context
|
||||||
|
3 - Reply to Telegram
|
||||||
|
*/
|
||||||
|
final String message =
|
||||||
|
Try.of(
|
||||||
|
() -> {
|
||||||
|
final Locale locale = Locale.forLanguageTag(chat.getLocale());
|
||||||
|
final ToolManager toolContext = localizedMessageFactory.create(locale);
|
||||||
|
final VelocityContext context =
|
||||||
|
new VelocityContext(toolContext.createContext());
|
||||||
|
context.put("firstName", update.message().chat().firstName());
|
||||||
|
context.put("programName", "Mezzotre");
|
||||||
|
|
||||||
|
final StringBuilder content = new StringBuilder();
|
||||||
|
final StringBuilderWriter stringBuilderWriter =
|
||||||
|
new StringBuilderWriter(content);
|
||||||
|
|
||||||
|
toolContext
|
||||||
|
.getVelocityEngine()
|
||||||
|
.mergeTemplate(
|
||||||
|
"template/command/start.vm",
|
||||||
|
StandardCharsets.UTF_8.name(),
|
||||||
|
context,
|
||||||
|
stringBuilderWriter);
|
||||||
|
|
||||||
|
stringBuilderWriter.close();
|
||||||
|
return content.toString();
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
log.trace("Start command - message to send back: " + message);
|
||||||
|
|
||||||
|
final ChatContext chatContext = chat.getChatContext();
|
||||||
|
chatContext.setLastMessageSentId(update.message().messageId());
|
||||||
|
chatContext.setStage(getTriggerKeyword());
|
||||||
|
chatContext.setStep(0);
|
||||||
|
chatContext.setPreviousMessageUnixTimestampInSeconds(update.message().date());
|
||||||
|
chat.setChatContext(chatContext);
|
||||||
|
chat.save();
|
||||||
|
|
||||||
|
return Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown));
|
||||||
|
},
|
||||||
|
threadPool);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command.di;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.telegram.command.Executor;import com.github.polpetta.mezzotre.telegram.command.Start;import com.google.common.io.Resources;import com.google.inject.AbstractModule;import com.google.inject.Provides;import org.apache.velocity.app.VelocityEngine;
|
||||||
|
import org.apache.velocity.runtime.RuntimeConstants;
|
||||||
|
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;import java.util.Set;
|
||||||
|
|
||||||
|
public class Command extends AbstractModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("commands")
|
||||||
|
public Set<Executor> getCommandExecutors(
|
||||||
|
Start start
|
||||||
|
) {
|
||||||
|
return Set.of(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public VelocityEngine getVelocityEngine() {
|
||||||
|
final VelocityEngine velocityEngine = new VelocityEngine();
|
||||||
|
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
|
||||||
|
velocityEngine.setProperty(
|
||||||
|
"classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
|
||||||
|
velocityEngine.init();
|
||||||
|
return velocityEngine;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.github.polpetta.mezzotre.util;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.*;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class Clock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current UNIX timestamp in milliseconds (so since 1970/01/01)
|
||||||
|
*/
|
||||||
|
public long now() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timestamp nowInTimestamp() {
|
||||||
|
return new Timestamp(now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date nowInDate() {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNowDateISO8601() {
|
||||||
|
final TimeZone tz = TimeZone.getTimeZone("UTC");
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'Z'");
|
||||||
|
df.setTimeZone(tz);
|
||||||
|
return df.format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNowDateTimeISO8601() {
|
||||||
|
final DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of("UTC"));
|
||||||
|
return df.format(Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDateISO86001(long unixTimestamp) {
|
||||||
|
final DateTimeFormatter df = DateTimeFormatter.ISO_DATE.withZone(ZoneId.of("UTC"));
|
||||||
|
return df.format(Instant.ofEpochMilli(unixTimestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDateTimeISO8601(long unixTimestamp) {
|
||||||
|
final DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of("UTC"));
|
||||||
|
return df.format(Instant.ofEpochMilli(unixTimestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Timestamp timestampFromString(String entry, String pattern) {
|
||||||
|
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
|
||||||
|
final LocalDate parse = LocalDate.parse(entry, dateTimeFormatter);
|
||||||
|
return new Timestamp(parse.toEpochSecond(LocalTime.MIDNIGHT, ZoneOffset.of("Z")) * 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Timestamp getTimestampFromUnixEpoch(long unixTimestamp) {
|
||||||
|
return new Timestamp(unixTimestamp);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.github.polpetta.mezzotre.util;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple UUID generator wrapper to make this code easily mockable in tests. It doesn't have any
|
||||||
|
* other purpose
|
||||||
|
*
|
||||||
|
* @author Davide Polonio
|
||||||
|
* @since 1.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class UUIDGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a standard Java {@link UUID}
|
||||||
|
*
|
||||||
|
* @return a {@link UUID}
|
||||||
|
*/
|
||||||
|
public UUID generate() {
|
||||||
|
return UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to get a {@link UUID} directly as {@link String}
|
||||||
|
*
|
||||||
|
* @return a {@link UUID} as {@link String}
|
||||||
|
*/
|
||||||
|
public String generateAsString() {
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.github.polpetta.mezzotre.util.di;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
|
public class ThreadPool extends AbstractModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("eventThreadPool")
|
||||||
|
public Executor getEventExecutor() {
|
||||||
|
return ForkJoinPool.commonPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("longJobThreadPool")
|
||||||
|
public Executor getLongJobExecutor() {
|
||||||
|
return Executors.newCachedThreadPool();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
-- apply changes
|
||||||
|
create table telegram_chat (
|
||||||
|
id bigint generated by default as identity not null,
|
||||||
|
chat_context jsonb not null default '{}'::jsonb not null,
|
||||||
|
locale varchar(5) not null default 'en-US' not null,
|
||||||
|
entry_created timestamptz not null,
|
||||||
|
entry_modified timestamptz not null,
|
||||||
|
constraint pk_telegram_chat primary key (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table registered_user (
|
||||||
|
id varchar(64) not null,
|
||||||
|
is_active boolean default false not null,
|
||||||
|
telegram_id bigint,
|
||||||
|
email_address varchar(256),
|
||||||
|
entry_created timestamptz not null,
|
||||||
|
entry_modified timestamptz not null,
|
||||||
|
constraint uq_registered_user_telegram_id unique (telegram_id),
|
||||||
|
constraint pk_registered_user primary key (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- foreign keys and indices
|
||||||
|
alter table registered_user add constraint fk_registered_user_telegram_id foreign key (telegram_id) references telegram_chat (id) on delete cascade on update restrict;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
entity-packages: com.github.polpetta.mezzotre.orm.model
|
||||||
|
transactional-packages: com.github.polpetta.mezzotre.orm.transaction
|
|
@ -0,0 +1,3 @@
|
||||||
|
start.hello=Hello
|
||||||
|
start.thisIs=This is
|
||||||
|
start.description=a simple bot focused on DnD content management! Please start by choosing a language down below.
|
|
@ -0,0 +1,3 @@
|
||||||
|
start.hello=Hello
|
||||||
|
start.thisIs=This is
|
||||||
|
start.description=a simple bot focused on DnD content management! Please start by choosing a language down below.
|
|
@ -0,0 +1,3 @@
|
||||||
|
start.hello=Ciao
|
||||||
|
start.thisIs=Questo è
|
||||||
|
start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto
|
|
@ -0,0 +1,3 @@
|
||||||
|
start.hello=Ciao
|
||||||
|
start.thisIs=Questo è
|
||||||
|
start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="15 seconds" debug="false">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.github.polpetta" level="INFO" />
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,4 @@
|
||||||
|
## https://velocity.apache.org/tools/2.0/apidocs/org/apache/velocity/tools/generic/ResourceTool.html
|
||||||
|
**$i18n.start.hello $firstName! 👋**
|
||||||
|
|
||||||
|
$i18n.start.thisIs _${programName}_, $i18n.start.description 👇
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.github.polpetta.mezzotre;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.helper.IntegrationAppFactory;
|
||||||
|
import com.github.polpetta.mezzotre.helper.Loader;
|
||||||
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
|
import io.ebean.Database;
|
||||||
|
import io.jooby.JoobyTest;
|
||||||
|
import io.jooby.StatusCode;
|
||||||
|
import java.io.IOException;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Tag("database")
|
||||||
|
@JoobyTest(
|
||||||
|
value = App.class,
|
||||||
|
factoryClass = IntegrationAppFactory.class,
|
||||||
|
factoryMethod = "loadCustomDbApplication")
|
||||||
|
public class IntegrationTest {
|
||||||
|
|
||||||
|
static OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration test example using OKHttpClient. The serverPort(int) argument is generated by
|
||||||
|
* Jooby. Jooby automatically set it as long it is declared as int and name it: serverPort.
|
||||||
|
*
|
||||||
|
* <p>Please refer to: https://jooby.io/#testing-integration-testing for more information.
|
||||||
|
*
|
||||||
|
* @param serverPort Server port (must be compiled with --parameters enabled).
|
||||||
|
* @throws IOException If something goes wrong.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSayWelcome(int serverPort) throws IOException {
|
||||||
|
Request req = new Request.Builder().url("http://localhost:" + serverPort).build();
|
||||||
|
|
||||||
|
try (Response rsp = client.newCall(req).execute()) {
|
||||||
|
assertEquals("Hello World!", rsp.body().string());
|
||||||
|
assertEquals(StatusCode.OK.value(), rsp.code());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.github.polpetta.mezzotre;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import com.google.inject.*;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import io.jooby.*;
|
||||||
|
import io.jooby.ebean.EbeanModule;
|
||||||
|
import io.jooby.flyway.FlywayModule;
|
||||||
|
import io.jooby.hikari.HikariModule;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class UnitTest {
|
||||||
|
|
||||||
|
private static class TestDI extends AbstractModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("hikariPoolExtension")
|
||||||
|
public Extension getHikariExtension() {
|
||||||
|
return mock(HikariModule.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("flyWayMigrationExtension")
|
||||||
|
public Extension getFlyWayMigrationExtension() {
|
||||||
|
return mock(FlywayModule.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("ebeanExtension")
|
||||||
|
public Extension getEbeanExtension() {
|
||||||
|
return mock(EbeanModule.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSayWelcome() {
|
||||||
|
final TestDI testDI = new TestDI();
|
||||||
|
final List<Module> abstractModules = new ArrayList<>();
|
||||||
|
abstractModules.add(testDI);
|
||||||
|
MockRouter router = new MockRouter(new App(Stage.DEVELOPMENT, abstractModules));
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
rsp -> {
|
||||||
|
assertEquals("Hello World!", rsp.value());
|
||||||
|
assertEquals(StatusCode.OK, rsp.getStatusCode());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.github.polpetta.mezzotre.helper;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.App;
|
||||||
|
import com.github.polpetta.mezzotre.InjectionModule;
|
||||||
|
import com.github.polpetta.mezzotre.route.di.Route;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import com.google.inject.Stage;
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.ebean.EbeanModule;
|
||||||
|
import io.jooby.flyway.FlywayModule;
|
||||||
|
import io.jooby.hikari.HikariModule;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
|
||||||
|
public class IntegrationAppFactory {
|
||||||
|
|
||||||
|
private static class DatabaseDI extends AbstractModule {
|
||||||
|
|
||||||
|
private final PostgreSQLContainer<?> container;
|
||||||
|
|
||||||
|
public DatabaseDI(PostgreSQLContainer<?> container) {
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("hikariPoolExtension")
|
||||||
|
public Extension getHikariExtension() throws Exception {
|
||||||
|
final Pair<Properties, Properties> propertiesPropertiesPair =
|
||||||
|
Loader.loadDefaultEbeanConfigWithPostgresSettings(container);
|
||||||
|
|
||||||
|
return new HikariModule(new HikariConfig(propertiesPropertiesPair.getRight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("flyWayMigrationExtension")
|
||||||
|
public Extension getFlyWayMigrationExtension() {
|
||||||
|
return new FlywayModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("ebeanExtension")
|
||||||
|
public Extension getEbeanExtension() {
|
||||||
|
return new EbeanModule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static App loadCustomDbApplication() {
|
||||||
|
final PostgreSQLContainer<?> container =
|
||||||
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
|
container.start();
|
||||||
|
final Route routeModule = new Route();
|
||||||
|
final DatabaseDI databaseDI = new DatabaseDI(container);
|
||||||
|
return new App(Stage.DEVELOPMENT, Set.of(databaseDI, routeModule));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.github.polpetta.mezzotre.helper;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.App;import com.google.common.io.Resources;import com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;import io.ebean.Database;import io.ebean.DatabaseFactory;import io.ebean.config.DatabaseConfig;import org.apache.commons.lang3.tuple.Pair;import org.testcontainers.containers.PostgreSQLContainer;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.util.Properties;public class Loader {
|
||||||
|
|
||||||
|
public static Pair<Properties, Properties> loadDefaultEbeanConfigWithPostgresSettings(
|
||||||
|
PostgreSQLContainer<?> container) throws IOException {
|
||||||
|
final URL ebeanRes = Resources.getResource("database-test.properties");
|
||||||
|
final URL hikariRes = Resources.getResource("hikari.properties");
|
||||||
|
final Properties ebeanConnectionProperties = new Properties();
|
||||||
|
final Properties hikariConnectionProperties = new Properties();
|
||||||
|
try (InputStream ebeanInputStream = ebeanRes.openStream();
|
||||||
|
InputStream hikariInputStream = hikariRes.openStream()) {
|
||||||
|
hikariConnectionProperties.load(hikariInputStream);
|
||||||
|
hikariConnectionProperties.put("username", container.getUsername());
|
||||||
|
hikariConnectionProperties.put("password", container.getPassword());
|
||||||
|
hikariConnectionProperties.put("jdbcUrl", container.getJdbcUrl());
|
||||||
|
|
||||||
|
ebeanConnectionProperties.load(ebeanInputStream);
|
||||||
|
ebeanConnectionProperties.put("datasource_db_username", container.getUsername());
|
||||||
|
ebeanConnectionProperties.put("datasource_db_password", container.getPassword());
|
||||||
|
ebeanConnectionProperties.put("datasource_db_databaseUrl", container.getJdbcUrl());
|
||||||
|
ebeanConnectionProperties.put("datasource_db_databaseDriver", "org.postgresql.Driver");
|
||||||
|
}
|
||||||
|
return Pair.of(ebeanConnectionProperties, hikariConnectionProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Database connectToDatabase(
|
||||||
|
Properties ebean,
|
||||||
|
Properties hikari
|
||||||
|
) {
|
||||||
|
final HikariDataSource hikariDataSource =
|
||||||
|
new HikariDataSource(new HikariConfig(hikari));
|
||||||
|
final DatabaseConfig databaseConfig = new DatabaseConfig();
|
||||||
|
databaseConfig.loadFromProperties(ebean);
|
||||||
|
databaseConfig.setDataSource(hikariDataSource);
|
||||||
|
|
||||||
|
return DatabaseFactory.create(databaseConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Database connectToDatabase(Pair<Properties, Properties> connectionProperties) {
|
||||||
|
return connectToDatabase(connectionProperties.getLeft(), connectionProperties.getRight());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.github.polpetta.mezzotre.helper;
|
||||||
|
|
||||||
|
public class TestConfig {
|
||||||
|
public static final String POSTGRES_DOCKER_IMAGE = "postgres:13-alpine";
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.github.polpetta.mezzotre.helper.Loader;
|
||||||
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
||||||
|
import com.github.polpetta.mezzotre.util.Clock;
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import io.ebean.Database;
|
||||||
|
import io.ebean.SqlRow;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.postgresql.util.PGobject;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Tag("database")
|
||||||
|
@Testcontainers
|
||||||
|
class TgChatIntegrationTest {
|
||||||
|
|
||||||
|
private static ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private final PostgreSQLContainer<?> postgresServer =
|
||||||
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
objectMapper = new ObjectMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
database =
|
||||||
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInsertEntryIntoDatabase() throws Exception {
|
||||||
|
|
||||||
|
final ChatContext chatContext =
|
||||||
|
new ChatContext.ChatContextBuilder()
|
||||||
|
.withStage("/example")
|
||||||
|
.withStep(2)
|
||||||
|
.withPreviousMessageUnixTimestampInSeconds(42)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final TgChat user = new TgChat(1234L, chatContext, "en");
|
||||||
|
|
||||||
|
user.save();
|
||||||
|
final String query = "select * from telegram_chat where id = ?";
|
||||||
|
final SqlRow savedChat = database.sqlQuery(query).setParameter(1234L).findOne();
|
||||||
|
|
||||||
|
assertNotNull(savedChat);
|
||||||
|
assertEquals(1234L, savedChat.getLong("id"));
|
||||||
|
assertEquals("en", savedChat.getString("locale"));
|
||||||
|
assertNotNull(savedChat.get("chat_context"));
|
||||||
|
assertEquals("jsonb", ((PGobject) savedChat.get("chat_context")).getType());
|
||||||
|
assertEquals(
|
||||||
|
chatContext,
|
||||||
|
objectMapper.readValue(
|
||||||
|
((PGobject) savedChat.get("chat_context")).getValue(), ChatContext.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRetrieveEntryInDatabase() throws Exception {
|
||||||
|
|
||||||
|
final Timestamp timestampFromUnixEpoch = Clock.getTimestampFromUnixEpoch(1L);
|
||||||
|
final ChatContext chatContext =
|
||||||
|
new ChatContext.ChatContextBuilder()
|
||||||
|
.withStage("/start")
|
||||||
|
.withStep(1)
|
||||||
|
.withPreviousMessageUnixTimestampInSeconds(42)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String insertQuery = "insert into telegram_chat values (?, ?::jsonb, ?, ?, ?)";
|
||||||
|
final int affectedRows =
|
||||||
|
database
|
||||||
|
.sqlUpdate(insertQuery)
|
||||||
|
.setParameter(1234L)
|
||||||
|
.setParameter(objectMapper.writeValueAsString(chatContext))
|
||||||
|
.setParameter("en-US")
|
||||||
|
.setParameter(timestampFromUnixEpoch)
|
||||||
|
.setParameter(timestampFromUnixEpoch)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals(1, affectedRows);
|
||||||
|
|
||||||
|
final TgChat got = new QTgChat().id.eq(1234L).findOne();
|
||||||
|
assertNotNull(got);
|
||||||
|
final ChatContext gotChatContext = got.getChatContext();
|
||||||
|
assertNotNull(gotChatContext);
|
||||||
|
assertEquals("/start", gotChatContext.getStage());
|
||||||
|
assertEquals(1, gotChatContext.getStep());
|
||||||
|
assertEquals(42, gotChatContext.getPreviousMessageUnixTimestampInSeconds());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.github.polpetta.mezzotre.orm.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.helper.Loader;
|
||||||
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.query.QUser;
|
||||||
|
import com.github.polpetta.mezzotre.util.Clock;
|
||||||
|
import io.ebean.Database;
|
||||||
|
import io.ebean.SqlRow;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Tag("database")
|
||||||
|
@Testcontainers
|
||||||
|
public class UserIntegrationTest {
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private final PostgreSQLContainer<?> postgresServer =
|
||||||
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
database =
|
||||||
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInsertEntryIntoDatabase() {
|
||||||
|
|
||||||
|
final User user = new User("1234", "example@example.com", true, null);
|
||||||
|
|
||||||
|
user.save();
|
||||||
|
final String query = "select * from registered_user where id = ?";
|
||||||
|
final SqlRow savedUser = database.sqlQuery(query).setParameter("1234").findOne();
|
||||||
|
|
||||||
|
assertNotNull(savedUser);
|
||||||
|
assertEquals("1234", savedUser.getString("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRetrieveEntryInDatabase() {
|
||||||
|
|
||||||
|
final Timestamp timestampFromUnixEpoch = Clock.getTimestampFromUnixEpoch(1L);
|
||||||
|
|
||||||
|
final String insertQuery =
|
||||||
|
"insert into registered_user (id, is_active, telegram_id, email_address, entry_created, entry_modified) values (?, ?, null, ?, ?, ?)";
|
||||||
|
final int affectedRows =
|
||||||
|
database
|
||||||
|
.sqlUpdate(insertQuery)
|
||||||
|
.setParameter("id123")
|
||||||
|
.setParameter(true)
|
||||||
|
.setParameter("example@example.com")
|
||||||
|
.setParameter(timestampFromUnixEpoch)
|
||||||
|
.setParameter(timestampFromUnixEpoch)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals(1, affectedRows);
|
||||||
|
|
||||||
|
final User id123 = new QUser().id.eq("id123").findOne();
|
||||||
|
assertNotNull(id123);
|
||||||
|
assertTrue(id123.isActive());
|
||||||
|
assertNull(id123.getTelegramId());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.github.polpetta.mezzotre.route;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.helper.Loader;
|
||||||
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.mezzotre.telegram.command.Router;
|
||||||
|
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.pengrad.telegrambot.TelegramBot;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.response.SendResponse;
|
||||||
|
import io.ebean.Database;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import io.jooby.Context;import io.jooby.MediaType;import org.junit.jupiter.api.*;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Tag("database")
|
||||||
|
@Testcontainers
|
||||||
|
class TelegramIntegrationTest {
|
||||||
|
|
||||||
|
private static Gson gson;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private final PostgreSQLContainer<?> postgresServer =
|
||||||
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
private Telegram telegram;
|
||||||
|
private TelegramBot fakeTelegramBot;
|
||||||
|
private Router fakeRouter;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
gson = new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
database =
|
||||||
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
|
|
||||||
|
fakeTelegramBot = mock(TelegramBot.class);
|
||||||
|
fakeRouter = mock(Router.class);
|
||||||
|
|
||||||
|
telegram =
|
||||||
|
new Telegram(
|
||||||
|
LoggerFactory.getLogger(getClass()),
|
||||||
|
fakeTelegramBot,
|
||||||
|
new GsonBuilder().setPrettyPrinting().create(),
|
||||||
|
Executors.newSingleThreadExecutor(),
|
||||||
|
new UUIDGenerator(),
|
||||||
|
fakeRouter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||||
|
void shouldReceiveAValidTelegramMessageUpdateAsResponse() throws Exception {
|
||||||
|
|
||||||
|
final SendMessage expectedBaseRequest = new SendMessage(1111111, "Hello world");
|
||||||
|
when(fakeRouter.process(any(TgChat.class), any(Update.class)))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(Optional.of(expectedBaseRequest)));
|
||||||
|
|
||||||
|
final SendResponse fakeResponse = mock(SendResponse.class);
|
||||||
|
when(fakeResponse.isOk()).thenReturn(true);
|
||||||
|
when(fakeTelegramBot.execute(any(SendMessage.class))).thenReturn(fakeResponse);
|
||||||
|
final Context fakeContext = mock(Context.class);
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/start\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
final CompletableFuture<String> integerCompletableFuture = telegram.incomingUpdate(fakeContext, update);
|
||||||
|
verify(fakeContext, times(1)).setResponseType(MediaType.JSON);
|
||||||
|
final String gotReply = integerCompletableFuture.get();
|
||||||
|
assertDoesNotThrow(() -> gotReply);
|
||||||
|
assertEquals(expectedBaseRequest.toWebhookResponse(), gotReply);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;import org.junit.jupiter.api.parallel.Execution;
|
||||||
|
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
|
class RouterTest {
|
||||||
|
|
||||||
|
private static Executor dummyEmptyExampleExecutor;
|
||||||
|
private static Executor anotherKeyWithResultExecutor;
|
||||||
|
private static Gson gson;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
gson = new Gson();
|
||||||
|
|
||||||
|
dummyEmptyExampleExecutor = mock(Executor.class);
|
||||||
|
when(dummyEmptyExampleExecutor.getTriggerKeyword()).thenReturn("/example");
|
||||||
|
when(dummyEmptyExampleExecutor.process(any(), any()))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||||
|
|
||||||
|
anotherKeyWithResultExecutor = mock(Executor.class);
|
||||||
|
when(anotherKeyWithResultExecutor.getTriggerKeyword()).thenReturn("/anotherExample");
|
||||||
|
when(anotherKeyWithResultExecutor.process(any(), any()))
|
||||||
|
.thenReturn(
|
||||||
|
CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception {
|
||||||
|
final Router router =
|
||||||
|
new Router(Set.of(dummyEmptyExampleExecutor), Executors.newSingleThreadExecutor());
|
||||||
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
|
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/example\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
|
||||||
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotExecution =
|
||||||
|
router.process(fakeChat, update);
|
||||||
|
assertDoesNotThrow(() -> gotExecution.get());
|
||||||
|
final Optional<BaseRequest<?, ?>> gotRequest = gotExecution.get();
|
||||||
|
assertTrue(gotRequest.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSelectRightExecutorAndReturnResult() throws Exception {
|
||||||
|
final Router router =
|
||||||
|
new Router(
|
||||||
|
Set.of(dummyEmptyExampleExecutor, anotherKeyWithResultExecutor),
|
||||||
|
Executors.newSingleThreadExecutor());
|
||||||
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
|
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/anotherExample\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
|
||||||
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotExecution =
|
||||||
|
router.process(fakeChat, update);
|
||||||
|
assertDoesNotThrow(() -> gotExecution.get());
|
||||||
|
final Optional<BaseRequest<?, ?>> gotRequestOpt = gotExecution.get();
|
||||||
|
assertDoesNotThrow(gotRequestOpt::get);
|
||||||
|
final BaseRequest<?, ?> gotMessage = gotRequestOpt.get();
|
||||||
|
assertInstanceOf(SendMessage.class, gotMessage);
|
||||||
|
final String message = (String) gotMessage.getParameters().get("text");
|
||||||
|
assertEquals("hello world", message);
|
||||||
|
assertEquals(1234L, (Long) gotMessage.getParameters().get("chat_id"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.helper.Loader;
|
||||||
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
|
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import io.ebean.Database;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import org.apache.velocity.app.VelocityEngine;
|
||||||
|
import org.apache.velocity.runtime.RuntimeConstants;
|
||||||
|
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Tag("database")
|
||||||
|
@Tag("velocity")
|
||||||
|
@Testcontainers
|
||||||
|
class StartIntegrationTest {
|
||||||
|
|
||||||
|
private static Gson gson;
|
||||||
|
|
||||||
|
@Container
|
||||||
|
private final PostgreSQLContainer<?> postgresServer =
|
||||||
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
|
|
||||||
|
private VelocityEngine velocityEngine;
|
||||||
|
private LocalizedMessageFactory localizedMessageFactory;
|
||||||
|
private Start start;
|
||||||
|
private Database database;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
gson = new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
database =
|
||||||
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
|
velocityEngine = new VelocityEngine();
|
||||||
|
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
|
||||||
|
velocityEngine.setProperty(
|
||||||
|
"resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
|
||||||
|
velocityEngine.init();
|
||||||
|
localizedMessageFactory = new LocalizedMessageFactory(velocityEngine);
|
||||||
|
|
||||||
|
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||||
|
|
||||||
|
start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateContextInTheDatabase() throws Exception {
|
||||||
|
final TgChat tgChat = new TgChat(1111111L, new ChatContext());
|
||||||
|
tgChat.save();
|
||||||
|
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/start\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
|
||||||
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotFuture = start.process(tgChat, update);
|
||||||
|
assertDoesNotThrow(() -> gotFuture.get());
|
||||||
|
final Optional<BaseRequest<?, ?>> gotMessageOptional = gotFuture.get();
|
||||||
|
assertDoesNotThrow(gotMessageOptional::get);
|
||||||
|
final BaseRequest<?, ?> gotMessage = gotMessageOptional.get();
|
||||||
|
assertInstanceOf(SendMessage.class, gotMessage);
|
||||||
|
final String message = (String) gotMessage.getParameters().get("text");
|
||||||
|
assertEquals(
|
||||||
|
"**Hello Test Firstname! \uD83D\uDC4B**\n\nThis is _Mezzotre_, a simple bot focused on DnD content management! Please start by choosing a language down below. \uD83D\uDC47",
|
||||||
|
message);
|
||||||
|
assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id"));
|
||||||
|
|
||||||
|
final TgChat retrievedTgChat = new QTgChat().id.eq(1111111L).findOne();
|
||||||
|
assertNotNull(retrievedTgChat);
|
||||||
|
final ChatContext gotChatContext = retrievedTgChat.getChatContext();
|
||||||
|
assertEquals(1441645532, gotChatContext.getPreviousMessageUnixTimestampInSeconds());
|
||||||
|
assertEquals(1365, gotChatContext.getLastMessageSentId());
|
||||||
|
assertEquals("/start", gotChatContext.getStage());
|
||||||
|
assertEquals(0, gotChatContext.getStep());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.github.polpetta.mezzotre.telegram.command;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||||
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
|
import com.github.polpetta.types.json.ChatContext;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import org.apache.velocity.app.VelocityEngine;
|
||||||
|
import org.apache.velocity.runtime.RuntimeConstants;
|
||||||
|
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Tag;import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.parallel.Execution;
|
||||||
|
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
@Tag("velocity")
|
||||||
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
|
class StartTest {
|
||||||
|
|
||||||
|
private VelocityEngine velocityEngine;
|
||||||
|
private LocalizedMessageFactory localizedMessageFactory;
|
||||||
|
private Start start;
|
||||||
|
private static Gson gson;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
gson = new Gson();
|
||||||
|
}@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
velocityEngine = new VelocityEngine();
|
||||||
|
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
|
||||||
|
velocityEngine.setProperty(
|
||||||
|
"resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
|
||||||
|
velocityEngine.init();
|
||||||
|
localizedMessageFactory = new LocalizedMessageFactory(velocityEngine);
|
||||||
|
|
||||||
|
final Logger fakeLog = mock(Logger.class);
|
||||||
|
|
||||||
|
start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), fakeLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReceiveHelloIntroduction() throws Exception {
|
||||||
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
|
when(fakeChat.getLocale()).thenReturn("en");
|
||||||
|
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||||
|
when(fakeChat.getId()).thenReturn(1111111L);
|
||||||
|
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/start\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
|
||||||
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotFuture =
|
||||||
|
start.process(fakeChat, update);
|
||||||
|
assertDoesNotThrow(() -> gotFuture.get());
|
||||||
|
final Optional<BaseRequest<?, ?>> gotMessageOptional = gotFuture.get();
|
||||||
|
assertDoesNotThrow(gotMessageOptional::get);
|
||||||
|
final BaseRequest<?, ?> gotMessage = gotMessageOptional.get();
|
||||||
|
assertInstanceOf(SendMessage.class, gotMessage);
|
||||||
|
final String message = (String) gotMessage.getParameters().get("text");
|
||||||
|
assertEquals(
|
||||||
|
"**Hello Test Firstname! \uD83D\uDC4B**\n\nThis is _Mezzotre_, a simple bot focused on DnD content management! Please start by choosing a language down below. \uD83D\uDC47",
|
||||||
|
message);
|
||||||
|
assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id"));
|
||||||
|
verify(fakeChat, times(1)).save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowErrorIfLocaleNonExists() {
|
||||||
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
|
// Do not set Locale on purpose
|
||||||
|
when(fakeChat.getId()).thenReturn(1111111L);
|
||||||
|
|
||||||
|
final Update update =
|
||||||
|
gson.fromJson(
|
||||||
|
"{\n"
|
||||||
|
+ "\"update_id\":10000,\n"
|
||||||
|
+ "\"message\":{\n"
|
||||||
|
+ " \"date\":1441645532,\n"
|
||||||
|
+ " \"chat\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"type\": \"private\",\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"message_id\":1365,\n"
|
||||||
|
+ " \"from\":{\n"
|
||||||
|
+ " \"last_name\":\"Test Lastname\",\n"
|
||||||
|
+ " \"id\":1111111,\n"
|
||||||
|
+ " \"first_name\":\"Test Firstname\",\n"
|
||||||
|
+ " \"username\":\"Testusername\"\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " \"text\":\"/start\"\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "}",
|
||||||
|
Update.class);
|
||||||
|
|
||||||
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotFuture =
|
||||||
|
start.process(fakeChat, update);
|
||||||
|
assertThrows(ExecutionException.class, gotFuture::get);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# general ebean properties
|
||||||
|
ebean.db.ddl.generate=true
|
||||||
|
ebean.db.ddl.run=true
|
||||||
|
|
||||||
|
ebean.db.generateMapping=true
|
||||||
|
ebean.db.dropCreate=true
|
||||||
|
ebean.db.create=true
|
||||||
|
|
||||||
|
#uncomment the following line if you, want to setup 'V' as prefix in your migration version string
|
||||||
|
ebean.db.migration.applyPrefix=V
|
||||||
|
ebean.db.migration.run=true
|
Loading…
Reference in New Issue