diff --git a/.github/busbot-code-style.xml b/.github/busbot-code-style.xml new file mode 100644 index 0000000..e5c04d4 --- /dev/null +++ b/.github/busbot-code-style.xml @@ -0,0 +1,51 @@ + + + + + + + + diff --git a/.github/checkstyle-configuration.xml b/.github/checkstyle-configuration.xml new file mode 100644 index 0000000..a4d8236 --- /dev/null +++ b/.github/checkstyle-configuration.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index a857cc8..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: SonarCloud -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened] -jobs: - build: - name: Build and analyze - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: 17 - distribution: temurin - - name: Cache SonarCloud packages - uses: actions/cache@v2 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v2 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=artmarchenko_busbot \ No newline at end of file diff --git a/.github/workflows/buildAndDeploy.yml b/.github/workflows/buildAndDeploy.yml new file mode 100644 index 0000000..788c734 --- /dev/null +++ b/.github/workflows/buildAndDeploy.yml @@ -0,0 +1,59 @@ +name: Build and deploy workflow + +on: + push: + branches: + - dev + - master + - 'release/**' +# - 'hotfix/**' + - 'feature/**' + +jobs: + package: + runs-on: ubuntu-latest + env: + BOT_NAME: ${{ secrets.BOT_NAME }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME}} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN}} + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: temurin + + - name: Publish to Docker Hub +# if: "startsWith(env.TAG_NAME, 'dev') || startsWith(env.TAG_NAME, 'release')" + run: >- + mvn + clean install + -PpushToDockerHub + + deploy: + needs: package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Login to Heroku Container registry + env: + HEROKU_API_KEY: ${{secrets.HEROKU_API_KEY}} + run: heroku container:login + - name: Push image to Heroku + env: + HEROKU_API_KEY: ${{secrets.HEROKU_API_KEY}} + run: + docker pull atmtrans/busbot && + docker tag atmtrans/busbot registry.heroku.com/busbot-atmtrans/worker && + docker push registry.heroku.com/busbot-atmtrans/worker + - name: Deploy application to Heroku + env: + HEROKU_API_KEY: ${{secrets.HEROKU_API_KEY}} + run: + heroku container:release -a busbot-atmtrans worker diff --git a/docs/media/git_flow_scheme.png b/docs/media/git_flow_scheme.png new file mode 100644 index 0000000..8426a5b Binary files /dev/null and b/docs/media/git_flow_scheme.png differ diff --git a/docs/project_versioning_policy.md b/docs/project_versioning_policy.md new file mode 100644 index 0000000..9e6a828 --- /dev/null +++ b/docs/project_versioning_policy.md @@ -0,0 +1,72 @@ +## Project versioning policy + +This document describes guide of GitFlow process established on this project. +Whenever you want to participate to develop some functionality or contribute +some changes to the current project, please follow rules described in this +file. + +___ + +### Git Flow + +The GitFlow schema approved for this project is illustrated by the following +sketch. + +![Git Flow Scheme](media/git_flow_scheme.png) + +* ```master``` branch: Contains stable, released code. Any direct pushes from +working (feature, dev) branches to this branch are NOT ALLOWED. All pull +requests will be rejected. + +* ```release``` branch: Temporary branch. Contains all workable, +requirement-matched code for future deploy. Support for preparing a new product +release. Allows you to fix small bugs and prepare different metadata for the +release. + +* ```dev``` branch: Develop branch should be inherited from latest master +version. Developers should to create a separate ```feature``` branches for +implement new functionality and open a pull request to push back a workable +code into this branch. Also, this branch allow strict merge of minor +corrections (e.g. typos, renaming, etc.) + +* ```feature``` branch: Should be created for implementation a new +functionality. The development of new features starts from the "dev" branch. +Once, when all work is done and matches all requirements this branch should +be merged into ```dev``` branch. This branch is temporary. Thus, after the pull +request is closed, this branch will be deleted. Naming of this branch are +numbered by tracking system (e.g. ZenHub) and should implement all +functionality described in the ticket. + +* ```hotfix``` branch: Branch created for immediate fix functionality of +released code if it goes wrong. The only branch which can be merged +directly to the master branch. + +* ```bugfix``` branch: Branch for fixes of non-critical errors in the app +behaviour. This branch should be inherited from dev, opened after +```bug report``` ticket were opened and should be numbered by ticket's number. +The correction which presented in this code should be covered by a test case +that catches the bug described in the ZenHub ticket. After all work is done +all commits should be merged in the ```dev``` branch. Naming in this branch +is equivalent to ```hotfix``` branch naming. + +* ```refactor``` branch: This branch should be created for the correction +of the implementation which is working correctly but the way of its +implementation has some flaws which are conflict with best practices, +have some security vulnerabilities, or violates +[Java Code Convention](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf). +This branch should be opened in according to Zenhub ticket. Result committed in +this branch should be merged into dev by opening a pull request. + +### Examples of naming: + +``` +GOOD: +feature/#2-Creating_new_functionality +hotfix/#245-Unexeptable_response_during_pay_process +bugfix/#252-Incorrect_route_building +refactor/#632-Ticket_booking_service_logic_refactor + +BAD: +Feature/230-WrongBranchName +Hotfix/#134-Fix-branch-naming +``` diff --git a/launch/docker-compose.yml b/launch/docker-compose.yml new file mode 100644 index 0000000..57d8d5f --- /dev/null +++ b/launch/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.8" + +services: + busbot: + image: atmtrans/busbot + environment: + BOT_TOKEN: ${BOT_TOKEN} + BOT_NAME: ${BOT_NAME} diff --git a/pom.xml b/pom.xml index f5c4a6a..e358997 100644 --- a/pom.xml +++ b/pom.xml @@ -5,20 +5,49 @@ org.springframework.boot spring-boot-starter-parent - 3.0.2 + 3.0.3 + com.atmtrans busbot - 0.0.1-SNAPSHOT + ${version.major}.${version.minor}.${version.patch}-${version.ci.revision} + busbot busbot + + + ${maven.multiModuleProjectDirectory}/.github/checkstyle-configuration.xml + 17 - artmarchenko - https://sonarcloud.io + 17 + 0 + 0 + 4 + SNAPSHOT + + 10.8.0 + 6.5.0 + 2.6.0 + 2.6.0 + + + 3.3.1 + dockerBuild + 3.2.1 + + + + pushToDockerHub + + build + + + + org.springframework.boot @@ -30,15 +59,100 @@ lombok true + org.springframework.boot spring-boot-starter-test test + + + org.telegram + telegrambots + ${telegrambots.version} + + + + org.tinylog + tinylog-api + ${tinylog-api.version} + + + + org.tinylog + tinylog-impl + ${tinylog-impl.version} + + + + com.google.cloud.tools + jib-maven-plugin + ${jib.version} + + + + ${env.DOCKER_HUB_USERNAME} + ${env.DOCKER_HUB_TOKEN} + + + + atmtrans/busbot + + ${project.version} + + + ${env.DOCKER_HUB_USERNAME} + ${env.DOCKER_HUB_TOKEN} + + + + com.atmtrans.busbot.BusbotApplication + USE_CURRENT_TIMESTAMP + + true + + + + package + + ${jib.goal} + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven.checkstyle.plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + ${checkstyle.configLocation} + ${project.build.sourceEncoding} + true + true + + + + validate + validate + + checkstyle + + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/com/atmtrans/busbot/BusbotApplication.java b/src/main/java/com/atmtrans/busbot/BusbotApplication.java index fea8f8b..8bec133 100644 --- a/src/main/java/com/atmtrans/busbot/BusbotApplication.java +++ b/src/main/java/com/atmtrans/busbot/BusbotApplication.java @@ -9,5 +9,4 @@ public class BusbotApplication { public static void main(String[] args) { SpringApplication.run(BusbotApplication.class, args); } - } diff --git a/src/main/java/com/atmtrans/busbot/TelegramBot.java b/src/main/java/com/atmtrans/busbot/TelegramBot.java new file mode 100644 index 0000000..212dbd6 --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/TelegramBot.java @@ -0,0 +1,59 @@ +package com.atmtrans.busbot; + +import com.atmtrans.busbot.controllers.TelegramBotController; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.bots.TelegramLongPollingBot; +import org.telegram.telegrambots.meta.TelegramBotsApi; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; +import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; +import org.tinylog.Logger; + + +@Component +public class TelegramBot extends TelegramLongPollingBot { + + private final String botName; + + private final TelegramBotController controller; + + public TelegramBot(@Value("${bot.token}") String token, @Value("${bot.name}") String botName, + TelegramBotController controller) throws TelegramApiException { + super(token); + + this.botName = botName; + this.controller = controller; + + Logger.info("TOKEN " + token); + new TelegramBotsApi(DefaultBotSession.class).registerBot(this); + } + + @SneakyThrows + @Override + public void onUpdateReceived(Update update) { + Logger.info("Update received"); + + if (update.hasMessage() && update.getMessage().hasText()) { + String messageText = update.getMessage().getText(); + long chatId = update.getMessage().getChatId(); + + if (messageText.startsWith("/reset")) { + controller.reset(chatId); + } else { + String reply = controller.talk(chatId, messageText); + SendMessage message = new SendMessage(); + message.setChatId(String.valueOf(chatId)); + message.setText(reply); + execute(message); + } + } + } + + @Override + public String getBotUsername() { + return botName; + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Action.java b/src/main/java/com/atmtrans/busbot/actions/Action.java new file mode 100644 index 0000000..9f4c1f5 --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Action.java @@ -0,0 +1,21 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + + +public interface Action { + + String handle(String msg); + + boolean canHandle(String msg); + + Collection children(); + + default Action resolveNext(String msg) { + return children().stream().filter(action -> action.canHandle(msg)).findFirst().orElseThrow(); + } + + default String gatName() { + return getClass().getSimpleName().toLowerCase(); + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Artem.java b/src/main/java/com/atmtrans/busbot/actions/Artem.java new file mode 100644 index 0000000..e74c806 --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Artem.java @@ -0,0 +1,27 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + +import lombok.RequiredArgsConstructor; + + +@RequiredArgsConstructor +public class Artem implements Action { + + private final Collection children; + + @Override + public String handle(String msg) { + return "Прівет Артьом"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("Артьом"); + } + + @Override + public Collection children() { + return children; + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Hello.java b/src/main/java/com/atmtrans/busbot/actions/Hello.java new file mode 100644 index 0000000..db2cb95 --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Hello.java @@ -0,0 +1,28 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + +import lombok.RequiredArgsConstructor; + + +@RequiredArgsConstructor +public class Hello implements Action { + + private final Collection children; + + + @Override + public String handle(String msg) { + return "Привіт, ти хто?"; + } + + @Override + public boolean canHandle(String msg) { + return true; + } + + @Override + public Collection children() { + return children; + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Javelin.java b/src/main/java/com/atmtrans/busbot/actions/Javelin.java new file mode 100644 index 0000000..e217fdc --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Javelin.java @@ -0,0 +1,17 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + + +public record Javelin(Collection children) implements Action { + + @Override + public String handle(String msg) { + return "Яке прикре самогубство"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("пики точеные") || msg.equals("a"); + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/NotOrc.java b/src/main/java/com/atmtrans/busbot/actions/NotOrc.java new file mode 100644 index 0000000..a45988f --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/NotOrc.java @@ -0,0 +1,26 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class NotOrc implements Action { + + private final Collection children; + + @Override + public String handle(String msg) { + return "Проходь дальше файно людино"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("не москаль"); + } + + @Override + public Collection children() { + return children; + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Orc.java b/src/main/java/com/atmtrans/busbot/actions/Orc.java new file mode 100644 index 0000000..aa5abee --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Orc.java @@ -0,0 +1,27 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + +import lombok.RequiredArgsConstructor; + + +@RequiredArgsConstructor +public class Orc implements Action { + + private final Collection children; + + @Override + public String handle(String msg) { + return "Маєш три варианти: a) пики точеные b) хуи дрочоные c) за щоку"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("москаль"); + } + + @Override + public Collection children() { + return children; + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Penis.java b/src/main/java/com/atmtrans/busbot/actions/Penis.java new file mode 100644 index 0000000..72a0ed4 --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Penis.java @@ -0,0 +1,17 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + + +public record Penis(Collection children) implements Action { + + @Override + public String handle(String msg) { + return "хароший выбор"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("за щоку") || msg.equals("c"); + } +} diff --git a/src/main/java/com/atmtrans/busbot/actions/Penises.java b/src/main/java/com/atmtrans/busbot/actions/Penises.java new file mode 100644 index 0000000..ce218cc --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/actions/Penises.java @@ -0,0 +1,16 @@ +package com.atmtrans.busbot.actions; + +import java.util.Collection; + +public record Penises(Collection children) implements Action { + + @Override + public String handle(String msg) { + return "Прикре самогубство"; + } + + @Override + public boolean canHandle(String msg) { + return msg.matches("хуи драченые") || msg.equals("b"); + } +} diff --git a/src/main/java/com/atmtrans/busbot/config/ActionConfig.java b/src/main/java/com/atmtrans/busbot/config/ActionConfig.java new file mode 100644 index 0000000..cf9b81d --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/config/ActionConfig.java @@ -0,0 +1,58 @@ +package com.atmtrans.busbot.config; + +import java.util.Collections; +import java.util.List; + +import com.atmtrans.busbot.actions.Action; +import com.atmtrans.busbot.actions.Artem; +import com.atmtrans.busbot.actions.Hello; +import com.atmtrans.busbot.actions.Javelin; +import com.atmtrans.busbot.actions.NotOrc; +import com.atmtrans.busbot.actions.Orc; +import com.atmtrans.busbot.actions.Penis; +import com.atmtrans.busbot.actions.Penises; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class ActionConfig { + + @Bean + public Action javelin() { + return new Javelin(Collections.emptyList()); + } + + @Bean + public Action penises() { + return new Penises(Collections.emptyList()); + } + + @Bean + public Action penis() { + return new Penis(Collections.emptyList()); + } + + @Bean + public Action orc(@Qualifier("javelin") Action javelin, @Qualifier("penises") Action penises, + @Qualifier("penis") Action penis) { + return new Orc(List.of(javelin, penises, penis)); + } + + @Bean + public Action notOrc() { + return new NotOrc(Collections.emptyList()); + } + + @Bean + public Action artem() { + return new Artem(Collections.emptyList()); + } + + @Bean + public Action hello(@Qualifier("orc") Action orc, @Qualifier("notOrc") Action notOrc, + @Qualifier("artem") Action artem) { + return new Hello(List.of(orc, notOrc, artem)); + } +} diff --git a/src/main/java/com/atmtrans/busbot/controllers/ActionController.java b/src/main/java/com/atmtrans/busbot/controllers/ActionController.java new file mode 100644 index 0000000..093522f --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/controllers/ActionController.java @@ -0,0 +1,57 @@ +package com.atmtrans.busbot.controllers; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.atmtrans.busbot.actions.Action; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + + +@Component +public class ActionController implements TelegramBotController { + private final Action helloAction; + + private final Map actions; + + private final Map dialogPositions = new HashMap<>(); + + public ActionController(Collection actions, @Qualifier("hello") Action hello) { + this.actions = actions.stream().collect(Collectors.toMap(Action::gatName, Function.identity())); + this.helloAction = hello; + } + + @Override + public String talk(long chatId, String msg) { + if (dialogPositions.containsKey(chatId)) { + var actionName = dialogPositions.get(chatId); + Action action; + try { + action = actions.get(actionName).resolveNext(msg); + } catch (NoSuchElementException e) { + reset(chatId); + return "Бувай"; + } + var reply = action.handle(msg); + if (action.children().isEmpty()) { + reset(chatId); + } else { + dialogPositions.put(chatId, action.gatName()); + } + return reply; + } else { + String reply = actions.get(helloAction.gatName()).handle(msg); + dialogPositions.put(chatId, helloAction.gatName()); + return reply; + } + } + + @Override + public void reset(long chatId) { + dialogPositions.remove(chatId); + } +} diff --git a/src/main/java/com/atmtrans/busbot/controllers/TelegramBotController.java b/src/main/java/com/atmtrans/busbot/controllers/TelegramBotController.java new file mode 100644 index 0000000..7d0c9ec --- /dev/null +++ b/src/main/java/com/atmtrans/busbot/controllers/TelegramBotController.java @@ -0,0 +1,9 @@ +package com.atmtrans.busbot.controllers; + + +public interface TelegramBotController { + + String talk(long chatId, String msg); + + void reset(long chatId); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..cfdcb7a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ - +bot.token=${BOT_TOKEN} +bot.name=${BOT_NAME} \ No newline at end of file