Repository: javarushcommunity/javarush-telegrambot Branch: main Commit: 8fcb5021d636 Files: 106 Total size: 144.5 KB Directory structure: gitextract_6x5b_faq/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ ├── improvement-request.md │ │ └── question.md │ ├── pull_request_template.md │ └── workflows/ │ └── maven.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Find_New_Posts_WF.bpmn ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── SET_UP_COMMANDS_BOT_FATHER ├── docker-compose-test.yml ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── github/ │ │ │ └── javarushcommunity/ │ │ │ └── jrtb/ │ │ │ ├── JavarushTelegramBotApplication.java │ │ │ ├── bot/ │ │ │ │ └── JavarushTelegramBot.java │ │ │ ├── command/ │ │ │ │ ├── AddGroupSubCommand.java │ │ │ │ ├── AdminHelpCommand.java │ │ │ │ ├── Command.java │ │ │ │ ├── CommandContainer.java │ │ │ │ ├── CommandName.java │ │ │ │ ├── CommandUtils.java │ │ │ │ ├── DeleteGroupSubCommand.java │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── ListGroupSubCommand.java │ │ │ │ ├── NoCommand.java │ │ │ │ ├── StartCommand.java │ │ │ │ ├── StatCommand.java │ │ │ │ ├── StopCommand.java │ │ │ │ ├── UnknownCommand.java │ │ │ │ └── annotation/ │ │ │ │ └── AdminCommand.java │ │ │ ├── dto/ │ │ │ │ ├── GroupStatDTO.java │ │ │ │ └── StatisticDTO.java │ │ │ ├── javarushclient/ │ │ │ │ ├── JavaRushGroupClient.java │ │ │ │ ├── JavaRushGroupClientImpl.java │ │ │ │ ├── JavaRushPostClient.java │ │ │ │ ├── JavaRushPostClientImpl.java │ │ │ │ └── dto/ │ │ │ │ ├── BaseUserInfo.java │ │ │ │ ├── GroupDiscussionInfo.java │ │ │ │ ├── GroupFilter.java │ │ │ │ ├── GroupInfo.java │ │ │ │ ├── GroupInfoType.java │ │ │ │ ├── GroupRequestArgs.java │ │ │ │ ├── GroupVisibilityStatus.java │ │ │ │ ├── GroupsCountRequestArgs.java │ │ │ │ ├── Language.java │ │ │ │ ├── LikeStatus.java │ │ │ │ ├── LikesInfo.java │ │ │ │ ├── MeGroupInfo.java │ │ │ │ ├── MeGroupInfoStatus.java │ │ │ │ ├── PostInfo.java │ │ │ │ ├── PostType.java │ │ │ │ ├── UserDiscussionInfo.java │ │ │ │ ├── UserPublicStatus.java │ │ │ │ └── VisibilityStatus.java │ │ │ ├── job/ │ │ │ │ └── FindNewPostsJob.java │ │ │ ├── repository/ │ │ │ │ ├── GroupSubRepository.java │ │ │ │ ├── TelegramUserRepository.java │ │ │ │ └── entity/ │ │ │ │ ├── GroupSub.java │ │ │ │ └── TelegramUser.java │ │ │ └── service/ │ │ │ ├── FindNewPostsService.java │ │ │ ├── FindNewPostsServiceImpl.java │ │ │ ├── GroupSubService.java │ │ │ ├── GroupSubServiceImpl.java │ │ │ ├── SendBotMessageService.java │ │ │ ├── SendBotMessageServiceImpl.java │ │ │ ├── StatisticsService.java │ │ │ ├── StatisticsServiceImpl.java │ │ │ ├── TelegramUserService.java │ │ │ └── TelegramUserServiceImpl.java │ │ └── resources/ │ │ ├── application-test.properties │ │ ├── application.properties │ │ ├── db/ │ │ │ └── migration/ │ │ │ ├── V00001__created_tg_user_table.sql │ │ │ ├── V00002__created_groupsub_many_to_many.sql │ │ │ ├── V00003__rename_last_article_id.sql │ │ │ └── V00004_change_chat_Id_type_to_Long.sql │ │ └── log4j2.xml │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── github/ │ │ └── javarushcommunity/ │ │ └── jrtb/ │ │ ├── command/ │ │ │ ├── AbstractCommandTest.java │ │ │ ├── AdminHelpCommandTest.java │ │ │ ├── CommandContainerTest.java │ │ │ ├── DeleteGroupSubCommandTest.java │ │ │ ├── HelpCommandTest.java │ │ │ ├── ListGroupSubCommandTest.java │ │ │ ├── NoCommandTest.java │ │ │ ├── StartCommandTest.java │ │ │ ├── StatCommandTest.java │ │ │ ├── StopCommandTest.java │ │ │ └── UnknownCommandTest.java │ │ ├── javarushclient/ │ │ │ ├── JavaRushGroupClientTest.java │ │ │ └── JavaRushPostClientTest.java │ │ ├── repository/ │ │ │ ├── GroupSubRepositoryIT.java │ │ │ └── TelegramUserRepositoryIT.java │ │ └── service/ │ │ ├── GroupSubServiceTest.java │ │ ├── SendBotMessageServiceTest.java │ │ └── StatisticsServiceImplTest.java │ └── resources/ │ └── sql/ │ ├── clearDbs.sql │ ├── fiveGroupSubsForUser.sql │ ├── fiveUsersForGroupSub.sql │ └── telegram_users.sql ├── start.sh └── stop.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: romankh3 --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "[FEATURE]" labels: feature assignees: romankh3 --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/improvement-request.md ================================================ --- name: Improvement request about: Suggest an improvment for this project title: "[IMPROVEMENT]" labels: enhancement assignees: romankh3 --- ** Describe your idea of the improvement ** ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: ask any questions, which are interested you title: "[QUESTION]" labels: 'question' assignees: romankh3 --- ================================================ FILE: .github/pull_request_template.md ================================================ # PR Details ## Description ## Related Issue ## Motivation and Context ## How Has This Been Tested ## Types of changes - [ ] Docs change / refactoring / dependency upgrade - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ================================================ FILE: .github/workflows/maven.yml ================================================ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Java CI with Maven on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up MySQL uses: mirromutth/mysql-action@v1.1 with: mysql version: '5.7' mysql database: 'dev_jrtb_db' mysql root password: 'root' mysql user: 'dev_jrtb_db_user' mysql password: 'dev_jrtb_db_password' - name: Set up JDK 1.11 uses: actions/setup-java@v1 with: java-version: 1.11 - name: Build with Maven run: mvn -B package --file pom.xml ================================================ FILE: .gitignore ================================================ .idea/ target build *.iml ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at roman.beskrovnyy@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: Dockerfile ================================================ FROM adoptopenjdk/openjdk11:ubi ARG JAR_FILE=target/*.jar ENV BOT_NAME=test.javarush_community_bot ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso ENV BOT_DB_USERNAME=jrtb_db_user ENV BOT_DB_PASSWORD=jrtb_db_password COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-Dspring.datasource.password=${BOT_DB_PASSWORD}", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-Dspring.datasource.username=${BOT_DB_USERNAME}", "-jar", "app.jar"] ================================================ FILE: Find_New_Posts_WF.bpmn ================================================ Flow_0yrxqwq Flow_0yrxqwq Flow_150dsht PT24H Flow_150dsht Flow_1g56vij Flow_1g56vij Flow_14geq43 Flow_0lqyzo8 Flow_0xev53g Flow_14geq43 Flow_0lqyzo8 Flow_0ux9vtc Flow_0ux9vtc Flow_0xev53g Runs every 15 minutes Find new posts for each group subscription since last time ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Javarush Telegram Bot ![87- Converted](https://user-images.githubusercontent.com/16310793/103351456-2861af00-4a58-11eb-9a64-1f69eff0631a.jpg) JavaRush Telegram bot from community for community. Written by developers, who learned in [Javarush](https://javarush.ru). ## Idea The main idea is to show how to create real application, which can be used by someone else. There are [set of posts](https://javarush.ru/groups/posts/2935-java-proekt-ot-a-do-ja-pishem-realjhnihy-proekt-dlja-portfolio), which are describing step by step guidelines of how it was created. ## MVP Scope As a user, I want to subscribe on group of posts and get notification via telegram-bot every time, when new post, related to group subscriptions, has come. # How it works Based on MVP Scope, we can specify next behaviours (here and after Telegram User, which is using JavaRush Telgegram bot will call User): - User can subscribe on group of posts - User can view list of group subscriptions on which user subscribes - User can unsubscribe from group of posts - User can set an inactive bot and do not receive notifications - User can restart getting notifications ## Find new posts workflow The workflow of finding new posts and send them to subscribers can be viewed here: ![Find_New_Posts_WF](https://user-images.githubusercontent.com/16310793/119827993-6c22ec80-bf02-11eb-8759-83bea483db93.png) ## Deployment Deployment process as easy as possible: Required software: - terminal for running bash scripts - docker - docker-compose to deploy application, switch to needed branch and run bash script: $ bash start.sh ${bot_username} ${bot_token} That's all. # Local development For local development and testing, use `docker-compose-test.yml`. Run command: ```shell docker-compose -f docker-compose-test.yml up ``` Next step, is to run SpringBoot app with configured **Edit Configuration** in which two env vars are provided: `bot.token=${BOT_TOKEN};bot.username=${BOT_USERNAME}` And add VM Options: `-Dspring.profiles.active=test ` With these configurations - run SpringBoot main method. # Technological stack - SpringBoot as a skeleton framework - Spring Scheduler as a task manager - MySQL database as a database for saving user and subscription info - Flyway database migration tool - Telegram-bot SpringBoot starter - Spring Data starter - Unirest - lib for working with REST calls ## Code of Conduct Please, follow [Code of Conduct](CODE_OF_CONDUCT.md) page. ## License This project is Apache License 2.0 - see the [LICENSE](LICENSE) file for details # Contributions Feel free to suggest new features via [github issue](https://github.com/javarushcommunity/javarush-telegrambot/issues/new). Note, that new features must be approved before start implement it to avoid the situation, when the time was spent, but the changes wouldn't added to the project. ================================================ FILE: RELEASE_NOTES.md ================================================ # Release Notes ## 1.0.0 Implemented all the logic, planned up to MVP: * User can subscribe on group of posts * User can view list of group subscriptions on which user subscribes * User can unsubscribe from group of posts * User can set an inactive bot and do not receive notifications * User can restart getting notifications * Admin has ability to see bot statistics ## 0.8.0-SNAPSHOT * JRTB-10: extended bot statistics for admins. ## 0.7.0-SNAPSHOT * JRTB-4: added ability to send notifications about new articles * JRTB-8: added ability to set inactive telegram user * JRTB-9: added ability to set active user and/or start using it. ## 0.6.0-SNAPSHOT * JRTB-7: added the ability to delete group subscription. ## 0.5.0-SNAPSHOT * JRTB-5: added ability to subscribe on group * JRTB-6: added ability to get a list of group subscriptions. ## 0.4.0-SNAPSHOT * JRTB-1: added repository layer. ## 0.3.0-SNAPSHOT * JRTB-13: added deployment process to the project ## 0.2.0-SNAPSHOT * JRTB-3: implemented Command pattern for handling Telegram Bot commands ## 0.1.0-SNAPSHOT * JRTB-2: added stub telegram bot * JRTB-0: added SpringBoot skeleton project ================================================ FILE: SET_UP_COMMANDS_BOT_FATHER ================================================ start - начать/восстановить работу с ботом stop - приостановить работу с ботом addgroupsub - подписаться на группу статей deletegroupsub - отписаться от группы статей listgroupsub - список групп, на которые подписан help - получить помощь в работе со мной ================================================ FILE: docker-compose-test.yml ================================================ version: '3.1' services: jrtb-db-dev: image: mysql:5.7 restart: always environment: MYSQL_DATABASE: 'dev_jrtb_db' # So you don't have to use root, but you can if you like MYSQL_USER: 'dev_jrtb_db_user' # You can use whatever password you like MYSQL_PASSWORD: 'dev_jrtb_db_password' # Password for root access MYSQL_ROOT_PASSWORD: 'root' ports: # : < MySQL Port running inside container> - '3306:3306' expose: # Opens port 3306 on the container - '3306' command: --character-set-server=utf8 --collation-server=utf8_general_ci ================================================ FILE: docker-compose.yml ================================================ version: '3.1' services: jrtb-bot: depends_on: - jrtb-db build: context: . environment: BOT_NAME: ${BOT_NAME} BOT_TOKEN: ${BOT_TOKEN} BOT_DB_USERNAME: ${BOT_DB_USERNAME} BOT_DB_PASSWORD: ${BOT_DB_PASSWORD} restart: always jrtb-db: image: mysql:5.7 restart: always environment: MYSQL_USER: ${BOT_DB_USERNAME} MYSQL_PASSWORD: ${BOT_DB_PASSWORD} MYSQL_DATABASE: 'jrtb_db' MYSQL_ROOT_PASSWORD: 'root' ports: - '3306:3306' expose: - '3306' command: --character-set-server=utf8 --collation-server=utf8_general_ci ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.7.RELEASE com.github.javarushcommunity javarush-telegrambot 1.0.0 Javarush TelegramBot Telegram bot for Javarush from community to community 11 5.0.1 3.11.01 3.11 org.telegram telegrambots-spring-boot-starter ${telegrambot.starter.version} com.konghq unirest-java ${unirest.version} org.springframework.boot spring-boot-starter org.flywaydb flyway-core org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java runtime org.projectlombok lombok true org.apache.commons commons-lang3 ${apache.commons.version} org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/JavarushTelegramBotApplication.java ================================================ package com.github.javarushcommunity.jrtb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling @SpringBootApplication public class JavarushTelegramBotApplication { public static void main(String[] args) { SpringApplication.run(JavarushTelegramBotApplication.class, args); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/bot/JavarushTelegramBot.java ================================================ package com.github.javarushcommunity.jrtb.bot; import com.github.javarushcommunity.jrtb.command.CommandContainer; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl; import com.github.javarushcommunity.jrtb.service.StatisticsService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.List; import static com.github.javarushcommunity.jrtb.command.CommandName.NO; /** * Telegram bot for Javarush Community from Javarush community. */ @Component public class JavarushTelegramBot extends TelegramLongPollingBot { public static String COMMAND_PREFIX = "/"; @Value("${bot.username}") private String username; @Value("${bot.token}") private String token; private final CommandContainer commandContainer; @Autowired public JavarushTelegramBot(TelegramUserService telegramUserService, JavaRushGroupClient groupClient, GroupSubService groupSubService, @Value("#{'${bot.admins}'.split(',')}") List admins, StatisticsService statisticsService) { this.commandContainer = new CommandContainer(new SendBotMessageServiceImpl(this), telegramUserService, groupClient, groupSubService, admins, statisticsService); } @Override public void onUpdateReceived(Update update) { if (update.hasMessage() && update.getMessage().hasText()) { String message = update.getMessage().getText().trim(); String username = update.getMessage().getFrom().getUserName(); if (message.startsWith(COMMAND_PREFIX)) { String commandIdentifier = message.split(" ")[0].toLowerCase(); commandContainer.findCommand(commandIdentifier, username).execute(update); } else { commandContainer.findCommand(NO.getCommandName(), username).execute(update); } } } @Override public String getBotUsername() { return username; } @Override public String getBotToken() { return token; } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/AddGroupSubCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.CommandName.ADD_GROUP_SUB; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage; import static java.util.Objects.isNull; import static org.apache.commons.lang3.StringUtils.SPACE; import static org.apache.commons.lang3.StringUtils.isNumeric; /** * Add Group subscription {@link Command}. */ //todo add unit test for the command logic. public class AddGroupSubCommand implements Command { private final SendBotMessageService sendBotMessageService; private final JavaRushGroupClient javaRushGroupClient; private final GroupSubService groupSubService; public AddGroupSubCommand(SendBotMessageService sendBotMessageService, JavaRushGroupClient javaRushGroupClient, GroupSubService groupSubService) { this.sendBotMessageService = sendBotMessageService; this.javaRushGroupClient = javaRushGroupClient; this.groupSubService = groupSubService; } @Override public void execute(Update update) { if (getMessage(update).equalsIgnoreCase(ADD_GROUP_SUB.getCommandName())) { sendGroupIdList(getChatId(update)); return; } String groupId = getMessage(update).split(SPACE)[1]; Long chatId = getChatId(update); if (isNumeric(groupId)) { GroupDiscussionInfo groupById = javaRushGroupClient.getGroupById(Integer.parseInt(groupId)); if (isNull(groupById.getId())) { sendGroupNotFound(chatId, groupId); } GroupSub savedGroupSub = groupSubService.save(chatId, groupById); sendBotMessageService.sendMessage(chatId, "Подписал на группу " + savedGroupSub.getTitle()); } else { sendNotValidGroupID(chatId, groupId); } } private void sendGroupNotFound(Long chatId, String groupId) { String groupNotFoundMessage = "Нет группы с ID = \"%s\""; sendBotMessageService.sendMessage(chatId, String.format(groupNotFoundMessage, groupId)); } private void sendNotValidGroupID(Long chatId, String groupId) { String groupNotFoundMessage = "Неправильный ID группы = \"%s\""; sendBotMessageService.sendMessage(chatId, String.format(groupNotFoundMessage, groupId)); ; } private void sendGroupIdList(Long chatId) { String groupIds = javaRushGroupClient.getGroupList(GroupRequestArgs.builder().build()).stream() .map(group -> String.format("%s - %s \n", group.getTitle(), group.getId())) .collect(Collectors.joining()); String message = "Чтобы подписаться на группу - передай команду вместе с ID группы. \n" + "Например: /addGroupSub 30 \n\n" + "я подготовил список всех групп - выбирай какую хочешь :) \n\n" + "имя группы - ID группы \n\n" + "%s"; sendBotMessageService.sendMessage(chatId, String.format(message, groupIds)); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/AdminHelpCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import org.telegram.telegrambots.meta.api.objects.Update; import static com.github.javarushcommunity.jrtb.command.CommandName.STAT; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; import static java.lang.String.format; /** * Admin Help {@link Command}. */ public class AdminHelpCommand implements Command { public static final String ADMIN_HELP_MESSAGE = format("✨Доступные команды админа✨\n\n" + "Получить статистику\n" + "%s - статистика бота\n", STAT.getCommandName()); private final SendBotMessageService sendBotMessageService; public AdminHelpCommand(SendBotMessageService sendBotMessageService) { this.sendBotMessageService = sendBotMessageService; } @Override public void execute(Update update) { sendBotMessageService.sendMessage(getChatId(update), ADMIN_HELP_MESSAGE); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/Command.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.telegram.telegrambots.meta.api.objects.Update; /** * Command interface for handling telegram-bot commands. */ public interface Command { /** * Main method, which is executing command logic. * * @param update provided {@link Update} object with all the needed data for command. */ void execute(Update update); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/CommandContainer.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.command.annotation.AdminCommand; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.StatisticsService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import com.google.common.collect.ImmutableMap; import java.util.List; import static com.github.javarushcommunity.jrtb.command.CommandName.*; import static java.util.Objects.nonNull; /** * Container of the {@link Command}s, which are using for handling telegram commands. */ public class CommandContainer { private final ImmutableMap commandMap; private final Command unknownCommand; private final List admins; public CommandContainer(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService, JavaRushGroupClient javaRushGroupClient, GroupSubService groupSubService, List admins, StatisticsService statisticsService) { this.admins = admins; commandMap = ImmutableMap.builder() .put(START.getCommandName(), new StartCommand(sendBotMessageService, telegramUserService)) .put(STOP.getCommandName(), new StopCommand(sendBotMessageService, telegramUserService)) .put(HELP.getCommandName(), new HelpCommand(sendBotMessageService)) .put(NO.getCommandName(), new NoCommand(sendBotMessageService)) .put(STAT.getCommandName(), new StatCommand(sendBotMessageService, statisticsService)) .put(ADD_GROUP_SUB.getCommandName(), new AddGroupSubCommand(sendBotMessageService, javaRushGroupClient, groupSubService)) .put(LIST_GROUP_SUB.getCommandName(), new ListGroupSubCommand(sendBotMessageService, telegramUserService)) .put(DELETE_GROUP_SUB.getCommandName(), new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService)) .put(ADMIN_HELP.getCommandName(), new AdminHelpCommand(sendBotMessageService)) .build(); unknownCommand = new UnknownCommand(sendBotMessageService); } public Command findCommand(String commandIdentifier, String username) { Command orDefault = commandMap.getOrDefault(commandIdentifier, unknownCommand); if (isAdminCommand(orDefault)) { if (admins.contains(username)) { return orDefault; } else { return unknownCommand; } } return orDefault; } private boolean isAdminCommand(Command command) { return nonNull(command.getClass().getAnnotation(AdminCommand.class)); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/CommandName.java ================================================ package com.github.javarushcommunity.jrtb.command; /** * Enumeration for {@link Command}'s. */ public enum CommandName { START("/start"), STOP("/stop"), HELP("/help"), ADMIN_HELP("/ahelp"), STAT("/stat"), NO("nocommand"), ADD_GROUP_SUB("/addgroupsub"), DELETE_GROUP_SUB("/deletegroupsub"), LIST_GROUP_SUB("/listgroupsub"); private final String commandName; CommandName(String commandName) { this.commandName = commandName; } public String getCommandName() { return commandName; } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/CommandUtils.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.telegram.telegrambots.meta.api.objects.Update; /** * Utils class for Commands. */ public class CommandUtils { /** * Get chatId from {@link Update} object. * * @param update provided {@link Update} * @return chatID from the provided {@link Update} object. */ public static Long getChatId(Update update) { return update.getMessage().getChatId(); } /** * Get text of the message from {@link Update} object. * * @param update provided {@link Update} * @return the text of the message from the provided {@link Update} object. */ public static String getMessage(Update update) { return update.getMessage().getText(); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/DeleteGroupSubCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.springframework.util.CollectionUtils; import org.telegram.telegrambots.meta.api.objects.Update; import javax.ws.rs.NotFoundException; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage; import static java.lang.String.format; import static org.apache.commons.lang3.StringUtils.SPACE; import static org.apache.commons.lang3.StringUtils.isNumeric; /** * Delete Group subscription {@link Command}. */ public class DeleteGroupSubCommand implements Command { private final SendBotMessageService sendBotMessageService; private final TelegramUserService telegramUserService; private final GroupSubService groupSubService; public DeleteGroupSubCommand(SendBotMessageService sendBotMessageService, GroupSubService groupSubService, TelegramUserService telegramUserService) { this.sendBotMessageService = sendBotMessageService; this.groupSubService = groupSubService; this.telegramUserService = telegramUserService; } @Override public void execute(Update update) { if (getMessage(update).equalsIgnoreCase(DELETE_GROUP_SUB.getCommandName())) { sendGroupIdList(getChatId(update)); return; } String groupId = getMessage(update).split(SPACE)[1]; Long chatId = getChatId(update); if (isNumeric(groupId)) { Optional optionalGroupSub = groupSubService.findById(Integer.valueOf(groupId)); if (optionalGroupSub.isPresent()) { GroupSub groupSub = optionalGroupSub.get(); TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new); groupSub.getUsers().remove(telegramUser); groupSubService.save(groupSub); sendBotMessageService.sendMessage(chatId, format("Удалил подписку на группу: %s", groupSub.getTitle())); } else { sendBotMessageService.sendMessage(chatId, "Не нашел такой группы =/"); } } else { sendBotMessageService.sendMessage(chatId, "неправильный формат ID группы.\n " + "ID должно быть целым положительным числом"); } } private void sendGroupIdList(Long chatId) { String message; List groupSubs = telegramUserService.findByChatId(chatId) .orElseThrow(NotFoundException::new) .getGroupSubs(); if (CollectionUtils.isEmpty(groupSubs)) { message = "Пока нет подписок на группы. Чтобы добавить подписку напиши /addGroupSub"; } else { String userGroupSubData = groupSubs.stream() .map(group -> format("%s - %s \n", group.getTitle(), group.getId())) .collect(Collectors.joining()); message = String.format("Чтобы удалить подписку на группу - передай комадну вместе с ID группы. \n" + "Например: /deleteGroupSub 16 \n\n" + "я подготовил список всех групп, на которые ты подписан) \n\n" + "имя группы - ID группы \n\n" + "%s", userGroupSubData); } sendBotMessageService.sendMessage(chatId, message); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/HelpCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import org.telegram.telegrambots.meta.api.objects.Update; import static com.github.javarushcommunity.jrtb.command.CommandName.*; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * Help {@link Command}. */ public class HelpCommand implements Command { private final SendBotMessageService sendBotMessageService; public static final String HELP_MESSAGE = String.format("✨Дотупные команды✨\n\n" + "Начать\\закончить работу с ботом:\n" + "%s - начать работу со мной\n" + "%s - приостановить работу со мной\n\n" + "Работа с подписками на группы:\n" + "%s - подписаться на группу статей\n" + "%s - отписаться от группы статей\n" + "%s - получить список групп, на которые подписан\n\n" + "%s - получить помощь в работе со мной\n", START.getCommandName(), STOP.getCommandName(), ADD_GROUP_SUB.getCommandName(), DELETE_GROUP_SUB.getCommandName(), LIST_GROUP_SUB.getCommandName(), HELP.getCommandName()); public HelpCommand(SendBotMessageService sendBotMessageService) { this.sendBotMessageService = sendBotMessageService; } @Override public void execute(Update update) { sendBotMessageService.sendMessage(getChatId(update), HELP_MESSAGE); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/ListGroupSubCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.springframework.util.CollectionUtils; import org.telegram.telegrambots.meta.api.objects.Update; import javax.ws.rs.NotFoundException; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * {@link Command} for getting list of {@link GroupSub}. */ public class ListGroupSubCommand implements Command { private final SendBotMessageService sendBotMessageService; private final TelegramUserService telegramUserService; public ListGroupSubCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) { this.sendBotMessageService = sendBotMessageService; this.telegramUserService = telegramUserService; } @Override public void execute(Update update) { //todo add exception handling TelegramUser telegramUser = telegramUserService.findByChatId(getChatId(update)) .orElseThrow(NotFoundException::new); String message; if(CollectionUtils.isEmpty(telegramUser.getGroupSubs())) { message = "Пока нет подписок на группы. Чтобы добавить подписку напиши /addGroupSub"; } else { String collectedGroups = telegramUser.getGroupSubs().stream() .map(it -> "Группа: " + it.getTitle() + " , ID = " + it.getId() + " \n") .collect(Collectors.joining()); message = String.format("Я нашел все подписки на группы: \n\n %s", collectedGroups); } sendBotMessageService.sendMessage(telegramUser.getChatId(), message); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/NoCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import org.telegram.telegrambots.meta.api.objects.Update; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * No {@link Command}. */ public class NoCommand implements Command { private final SendBotMessageService sendBotMessageService; public static final String NO_MESSAGE = "Я поддерживаю команды, начинающиеся со слеша(/).\n" + "Чтобы посмотреть список комманд введи /help"; public NoCommand(SendBotMessageService sendBotMessageService) { this.sendBotMessageService = sendBotMessageService; } @Override public void execute(Update update) { sendBotMessageService.sendMessage(getChatId(update), NO_MESSAGE); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/StartCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.telegram.telegrambots.meta.api.objects.Update; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * Start {@link Command}. */ public class StartCommand implements Command { private final SendBotMessageService sendBotMessageService; private final TelegramUserService telegramUserService; public final static String START_MESSAGE = "Привет. Я Javarush Telegram Bot.\n " + "Я помогу тебе быть в курсе последних статей тех авторов, которые тебе интересны.\n\n" + "Нажимай /addGroupSub чтобы подписаться на группу статей в JavaRush.\n" + "Не знаешь о чем я? Напиши /help, чтобы узнать что я умею."; public StartCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) { this.sendBotMessageService = sendBotMessageService; this.telegramUserService = telegramUserService; } @Override public void execute(Update update) { Long chatId = getChatId(update); telegramUserService.findByChatId(chatId).ifPresentOrElse( user -> { user.setActive(true); telegramUserService.save(user); }, () -> { TelegramUser telegramUser = new TelegramUser(); telegramUser.setActive(true); telegramUser.setChatId(chatId); telegramUserService.save(telegramUser); }); sendBotMessageService.sendMessage(chatId, START_MESSAGE); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/StatCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.command.annotation.AdminCommand; import com.github.javarushcommunity.jrtb.dto.StatisticDTO; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.StatisticsService; import org.springframework.beans.factory.annotation.Autowired; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * Statistics {@link Command}. */ @AdminCommand public class StatCommand implements Command { private final StatisticsService statisticsService; private final SendBotMessageService sendBotMessageService; public final static String STAT_MESSAGE = "✨Подготовил статистику✨\n" + "- Количество активных пользователей: %s\n" + "- Количество неактивных пользователей: %s\n" + "- Среднее количество групп на одного пользователя: %s\n\n" + "Информация по активным группам:\n" + "%s"; @Autowired public StatCommand(SendBotMessageService sendBotMessageService, StatisticsService statisticsService) { this.sendBotMessageService = sendBotMessageService; this.statisticsService = statisticsService; } @Override public void execute(Update update) { StatisticDTO statisticDTO = statisticsService.countBotStatistic(); String collectedGroups = statisticDTO.getGroupStatDTOs().stream() .map(it -> String.format("%s (id = %s) - %s подписчиков", it.getTitle(), it.getId(), it.getActiveUserCount())) .collect(Collectors.joining("\n")); sendBotMessageService.sendMessage(getChatId(update), String.format(STAT_MESSAGE, statisticDTO.getActiveUserCount(), statisticDTO.getInactiveUserCount(), statisticDTO.getAverageGroupCountByUser(), collectedGroups)); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/StopCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.Optional; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * Stop {@link Command}. */ public class StopCommand implements Command { private final SendBotMessageService sendBotMessageService; private final TelegramUserService telegramUserService; public static final String STOP_MESSAGE = "Деактивировал все твои подписки \uD83D\uDE1F.\n" + "Ты всегда можешь вернуться нажав /start"; public StopCommand(SendBotMessageService sendBotMessageService, TelegramUserService telegramUserService) { this.sendBotMessageService = sendBotMessageService; this.telegramUserService = telegramUserService; } @Override public void execute(Update update) { sendBotMessageService.sendMessage(getChatId(update), STOP_MESSAGE); telegramUserService.findByChatId(getChatId(update)) .ifPresent(it -> { it.setActive(false); telegramUserService.save(it); }); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/UnknownCommand.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import org.telegram.telegrambots.meta.api.objects.Update; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; /** * Unknown {@link Command}. */ public class UnknownCommand implements Command { public static final String UNKNOWN_MESSAGE = "Не понимаю тебя \uD83D\uDE1F, напиши /help чтобы узнать что я понимаю."; private final SendBotMessageService sendBotMessageService; public UnknownCommand(SendBotMessageService sendBotMessageService) { this.sendBotMessageService = sendBotMessageService; } @Override public void execute(Update update) { sendBotMessageService.sendMessage(getChatId(update), UNKNOWN_MESSAGE); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/command/annotation/AdminCommand.java ================================================ package com.github.javarushcommunity.jrtb.command.annotation; import com.github.javarushcommunity.jrtb.command.Command; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Mark if {@link Command} can be viewed only by admins. */ @Retention(RUNTIME) public @interface AdminCommand { } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/dto/GroupStatDTO.java ================================================ package com.github.javarushcommunity.jrtb.dto; import lombok.Data; import lombok.EqualsAndHashCode; /** * DTO for showing group id and title without data */ @Data @EqualsAndHashCode(exclude = {"title", "activeUserCount"}) public class GroupStatDTO { private final Integer id; private final String title; private final Integer activeUserCount; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/dto/StatisticDTO.java ================================================ package com.github.javarushcommunity.jrtb.dto; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.List; /** * DTO for getting bot statistics. */ @Data @EqualsAndHashCode public class StatisticDTO { private final int activeUserCount; private final int inactiveUserCount; private final List groupStatDTOs; private final double averageGroupCountByUser; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushGroupClient.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs; import java.util.List; /** * Client for Javarush Open API corresponds to Groups. */ public interface JavaRushGroupClient { /** * Get all the {@link GroupInfo} filtered by provided {@link GroupRequestArgs}. * * @param requestArgs provided {@link GroupRequestArgs}. * @return the collection of the {@link GroupInfo} objects. */ List getGroupList(GroupRequestArgs requestArgs); /** * Get all the {@link GroupDiscussionInfo} filtered by provided {@link GroupRequestArgs}. * * @param requestArgs provided {@link GroupRequestArgs} * @return the collection of the {@link GroupDiscussionInfo} objects. */ List getGroupDiscussionList(GroupRequestArgs requestArgs); /** * Get count of groups filtered by provided {@link GroupRequestArgs}. * * @param countRequestArgs provided {@link GroupsCountRequestArgs}. * @return the count of the groups. */ Integer getGroupCount(GroupsCountRequestArgs countRequestArgs); /** * Get {@link GroupDiscussionInfo} by provided ID. * * @param id provided ID. * @return {@link GroupDiscussionInfo} object. */ GroupDiscussionInfo getGroupById(Integer id); Integer findLastPostId(Integer groupSub); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushGroupClientImpl.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.*; import kong.unirest.GenericType; import kong.unirest.Unirest; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; import static org.springframework.util.CollectionUtils.isEmpty; /** * Implementation of the {@link JavaRushGroupClient} interface. */ @Component public class JavaRushGroupClientImpl implements JavaRushGroupClient { private final String javarushApiGroupPath; private final String getJavarushApiPostPath; public JavaRushGroupClientImpl(@Value("${javarush.api.path}") String javarushApi) { this.javarushApiGroupPath = javarushApi + "/groups"; this.getJavarushApiPostPath = javarushApi + "/posts"; } @Override public List getGroupList(GroupRequestArgs requestArgs) { return Unirest.get(javarushApiGroupPath) .queryString(requestArgs.populateQueries()) .asObject(new GenericType>() { }) .getBody(); } @Override public List getGroupDiscussionList(GroupRequestArgs requestArgs) { return Unirest.get(javarushApiGroupPath) .queryString(requestArgs.populateQueries()) .asObject(new GenericType>() { }) .getBody(); } @Override public Integer getGroupCount(GroupsCountRequestArgs countRequestArgs) { return Integer.valueOf( Unirest.get(String.format("%s/count", javarushApiGroupPath)) .queryString(countRequestArgs.populateQueries()) .asString() .getBody() ); } @Override public GroupDiscussionInfo getGroupById(Integer id) { return Unirest.get(String.format("%s/group%s", javarushApiGroupPath, id.toString())) .asObject(GroupDiscussionInfo.class) .getBody(); } @Override public Integer findLastPostId(Integer groupSubId) { List posts = Unirest.get(getJavarushApiPostPath) .queryString("order", "NEW") .queryString("groupKid", groupSubId.toString()) .queryString("limit", "1") .asObject(new GenericType>() { }) .getBody(); return isEmpty(posts) ? 0 : Optional.ofNullable(posts.get(0)).map(PostInfo::getId).orElse(0); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushPostClient.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo; import java.util.List; /** * Client for Javarush Open API corresponds to Posts. */ public interface JavaRushPostClient { /** * Find new posts since lastPostId in provided group. * * @param groupId provided group ID. * @param lastPostId provided last post ID. * @return the collection of the new {@link PostInfo}. */ List findNewPosts(Integer groupId, Integer lastPostId); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushPostClientImpl.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo; import kong.unirest.GenericType; import kong.unirest.Unirest; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component public class JavaRushPostClientImpl implements JavaRushPostClient { private final String javarushApiPostPath; public JavaRushPostClientImpl(@Value("${javarush.api.path}") String javarushApi) { this.javarushApiPostPath = javarushApi + "/posts"; } @Override public List findNewPosts(Integer groupId, Integer lastPostId) { List lastPostsByGroup = Unirest.get(javarushApiPostPath) .queryString("order", "NEW") .queryString("groupKid", groupId) .queryString("limit", 15) .asObject(new GenericType>() { }).getBody(); List newPosts = new ArrayList<>(); for (PostInfo post : lastPostsByGroup) { if (lastPostId.equals(post.getId())) { return newPosts; } newPosts.add(post); } return newPosts; } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/BaseUserInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; /** * DTO, which represents base user information. */ @Data public class BaseUserInfo { private String city; private String country; private String displayName; private Integer id; private String job; private String key; private Integer level; private String pictureUrl; private String position; private UserPublicStatus publicStatus; private String publicStatusMessage; private Integer rating; private Integer userId; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupDiscussionInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; /** * Group discussion info class. */ @EqualsAndHashCode(callSuper = true) @Data @ToString(callSuper = true) public class GroupDiscussionInfo extends GroupInfo { private UserDiscussionInfo userDiscussionInfo; private Integer commentsCount; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupFilter.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Filters for group requests. */ public enum GroupFilter { UNKNOWN, MY, ALL } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; import lombok.ToString; /** * Group Info DTO class. */ @Data @ToString public class GroupInfo { private Integer id; private String avatarUrl; private String createTime; private String description; private String key; private Integer levelToEditor; private MeGroupInfo meGroupInfo; private String pictureUrl; private String title; private GroupInfoType type; private Integer userCount; private GroupVisibilityStatus visibilityStatus; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupInfoType.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Group Info type; */ public enum GroupInfoType { UNKNOWN, CITY, COMPANY, COLLEGE, TECH, SPECIAL, COUNTRY } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupRequestArgs.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.*; import java.util.HashMap; import java.util.Map; import static java.util.Objects.nonNull; /** * Request arguments for group requests. */ @Builder @Getter public class GroupRequestArgs { private final String query; private final GroupInfoType type; private final GroupFilter filter; /** * specified where to start getting groups */ private final Integer offset; /** * Limited number of groups. */ private final Integer limit; public Map populateQueries() { Map queries = new HashMap<>(); if(nonNull(query)) { queries.put("query", query); } if(nonNull(type)) { queries.put("type", type); } if(nonNull(filter)) { queries.put("filter", filter); } if(nonNull(offset)) { queries.put("offset", offset); } if(nonNull(limit)) { queries.put("limit", limit); } return queries; } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupVisibilityStatus.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Group Visibility status. */ public enum GroupVisibilityStatus { UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/GroupsCountRequestArgs.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Builder; import lombok.Getter; import java.util.HashMap; import java.util.Map; import static java.util.Objects.nonNull; /** * Request arguments for group count requests. */ @Builder @Getter public class GroupsCountRequestArgs { private final String query; private final GroupInfoType type; private final GroupFilter filter; public Map populateQueries() { Map queries = new HashMap<>(); if (nonNull(query)) { queries.put("query", query); } if (nonNull(type)) { queries.put("type", type); } if (nonNull(filter)) { queries.put("filter", filter); } return queries; } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/Language.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents languages. */ public enum Language { UNKNOWN, ENGLISH, GERMAN, SPANISH, HINDI, FRENCH, PORTUGUESE, POLISH, BENGALI, PUNJABI, CHINESE, ITALIAN, INDONESIAN, MARATHI, TAMIL, TELUGU, JAPANESE, KOREAN, URDU, TAIWANESE, NETHERLANDS, RUSSIAN, UKRAINIAN } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/LikeStatus.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents like's status. */ public enum LikeStatus { UNKNOWN, LIKE, HOT, FOLLOW, FAVORITE, SOLUTION, HELPFUL, ARTICLE, OSCAR, DISLIKE, WRONG, SPAM, ABUSE, FOUL, TROLLING, OFFTOPIC, DUPLICATE, DIRTY, OUTDATED, BORING, UNCLEAR, HARD, EASY, FAKE, SHAM, AWFUL } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/LikesInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents like's information. */ public class LikesInfo { private Integer count; private LikeStatus status; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/MeGroupInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; /** * Group information related to authorized user. If there is no user - will be null. */ @Data public class MeGroupInfo { private MeGroupInfoStatus status; private Integer userGroupId; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/MeGroupInfoStatus.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * Member group status. */ public enum MeGroupInfoStatus { UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/PostInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; /** * DTO, which represents post information. */ @Data public class PostInfo { private BaseUserInfo authorInfo; private Integer commentsCount; private String content; private Long createdTime; private String description; private GroupInfo groupInfo; private Integer id; private String key; private Language language; private LikesInfo likesInfo; private GroupInfo originalGroupInfo; private String pictureUrl; private Double rating; private Integer ratingCount; private String title; private PostType type; private Long updatedTime; private UserDiscussionInfo userDiscussionInfo; private Integer views; private VisibilityStatus visibilityStatus; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/PostType.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents post types. */ public enum PostType { UNKNOWN, USUAL, INNER_LINK, OUTER_LINK } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/UserDiscussionInfo.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; import lombok.Data; /** * DTO for User discussion info. */ @Data public class UserDiscussionInfo { private Boolean isBookmarked; private Integer lastTime; private Integer newCommentsCount; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/UserPublicStatus.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents user public status. */ public enum UserPublicStatus { UNKNOWN, BEGINNER, ACTIVE, STRONG, GRADUATED, INTERNSHIP_IN_PROGRESS, INTERNSHIP_COMPLETED, RESUME_COMPLETED, LOOKING_FOR_JOB, HAVE_JOB; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/javarushclient/dto/VisibilityStatus.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient.dto; /** * DTO, which represents visibility status. */ public enum VisibilityStatus { UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/job/FindNewPostsJob.java ================================================ package com.github.javarushcommunity.jrtb.job; import com.github.javarushcommunity.jrtb.service.FindNewPostsService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneOffset; /** * Job for finding new posts. */ @Slf4j @Component public class FindNewPostsJob { private final FindNewPostsService findNewPostsService; @Autowired public FindNewPostsJob(FindNewPostsService findNewPostsService) { this.findNewPostsService = findNewPostsService; } @Scheduled(fixedRateString = "${bot.recountNewPostFixedRate}") public void findNewPosts() { LocalDateTime start = LocalDateTime.now(); log.info("Find new posts job started."); findNewPostsService.findNewPosts(); LocalDateTime end = LocalDateTime.now(); log.info("Find new posts job finished. Took seconds: {}", end.toEpochSecond(ZoneOffset.UTC) - start.toEpochSecond(ZoneOffset.UTC)); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/repository/GroupSubRepository.java ================================================ package com.github.javarushcommunity.jrtb.repository; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * {@link Repository} for {@link GroupSub} entity. */ @Repository public interface GroupSubRepository extends JpaRepository { } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/repository/TelegramUserRepository.java ================================================ package com.github.javarushcommunity.jrtb.repository; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; /** * {@link Repository} for handling with {@link TelegramUser} entity. */ @Repository public interface TelegramUserRepository extends JpaRepository { List findAllByActiveTrue(); List findAllByActiveFalse(); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/repository/entity/GroupSub.java ================================================ package com.github.javarushcommunity.jrtb.repository.entity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.persistence.*; import java.util.ArrayList; import java.util.List; import static java.util.Objects.isNull; @Data @Entity @Table(name = "group_sub") @EqualsAndHashCode(exclude = "users") public class GroupSub { @Id private Integer id; @Column(name = "title") private String title; @Column(name = "last_post_id") private Integer lastPostId; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "group_x_user", joinColumns = @JoinColumn(name = "group_sub_id"), inverseJoinColumns = @JoinColumn(name = "user_id") ) private List users; public void addUser(TelegramUser telegramUser) { if (isNull(users)) { users = new ArrayList<>(); } users.add(telegramUser); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/repository/entity/TelegramUser.java ================================================ package com.github.javarushcommunity.jrtb.repository.entity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.persistence.*; import java.util.List; /** * Telegram User entity. */ @Data @Entity @Table(name = "tg_user") @EqualsAndHashCode(exclude = "groupSubs") public class TelegramUser { @Id @Column(name = "chat_id") private Long chatId; @Column(name = "active") private boolean active; @ManyToMany(mappedBy = "users", fetch = FetchType.EAGER) private List groupSubs; } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/FindNewPostsService.java ================================================ package com.github.javarushcommunity.jrtb.service; /** * Service for finding new posts. */ public interface FindNewPostsService { /** * Find new posts and notify subscribers about it. */ void findNewPosts(); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/FindNewPostsServiceImpl.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushPostClient; import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Service public class FindNewPostsServiceImpl implements FindNewPostsService { public static final String JAVARUSH_WEB_POST_FORMAT = "https://javarush.ru/groups/posts/%s"; private final GroupSubService groupSubService; private final JavaRushPostClient javaRushPostClient; private final SendBotMessageService sendMessageService; @Autowired public FindNewPostsServiceImpl(GroupSubService groupSubService, JavaRushPostClient javaRushPostClient, SendBotMessageService sendMessageService) { this.groupSubService = groupSubService; this.javaRushPostClient = javaRushPostClient; this.sendMessageService = sendMessageService; } @Override public void findNewPosts() { groupSubService.findAll().forEach(gSub -> { List newPosts = javaRushPostClient.findNewPosts(gSub.getId(), gSub.getLastPostId()); setNewLastPostId(gSub, newPosts); notifySubscribersAboutNewPosts(gSub, newPosts); }); } private void notifySubscribersAboutNewPosts(GroupSub gSub, List newPosts) { Collections.reverse(newPosts); List messagesWithNewPosts = newPosts.stream() .map(post -> String.format("✨Вышла новая статья %s в группе %s.✨\n\n" + "Описание: %s\n\n" + "Ссылка: %s\n", post.getTitle(), gSub.getTitle(), post.getDescription(), getPostUrl(post.getKey()))) .collect(Collectors.toList()); gSub.getUsers().stream() .filter(TelegramUser::isActive) .forEach(it -> sendMessageService.sendMessage(it.getChatId(), messagesWithNewPosts)); } private void setNewLastPostId(GroupSub gSub, List newPosts) { newPosts.stream().mapToInt(PostInfo::getId).max() .ifPresent(id -> { gSub.setLastPostId(id); groupSubService.save(gSub); }); } private String getPostUrl(String key) { return String.format(JAVARUSH_WEB_POST_FORMAT, key); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/GroupSubService.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import java.util.List; import java.util.Optional; /** * Service for manipulating with {@link GroupSub}. */ public interface GroupSubService { GroupSub save(Long chatId, GroupDiscussionInfo groupDiscussionInfo); GroupSub save(GroupSub groupSub); Optional findById(Integer id); List findAll(); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/GroupSubServiceImpl.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.repository.GroupSubRepository; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.ws.rs.NotFoundException; import java.util.List; import java.util.Optional; @Service public class GroupSubServiceImpl implements GroupSubService { private final GroupSubRepository groupSubRepository; private final TelegramUserService telegramUserService; private final JavaRushGroupClient javaRushGroupClient; @Autowired public GroupSubServiceImpl(GroupSubRepository groupSubRepository, TelegramUserService telegramUserService, JavaRushGroupClient javaRushGroupClient) { this.groupSubRepository = groupSubRepository; this.telegramUserService = telegramUserService; this.javaRushGroupClient = javaRushGroupClient; } @Override public GroupSub save(Long chatId, GroupDiscussionInfo groupDiscussionInfo) { TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new); //TODO add exception handling GroupSub groupSub; Optional groupSubFromDB = groupSubRepository.findById(groupDiscussionInfo.getId()); if (groupSubFromDB.isPresent()) { groupSub = groupSubFromDB.get(); Optional first = groupSub.getUsers().stream() .filter(it -> it.getChatId().equals(chatId)) .findFirst(); if (first.isEmpty()) { groupSub.addUser(telegramUser); } } else { groupSub = new GroupSub(); groupSub.addUser(telegramUser); groupSub.setLastPostId(javaRushGroupClient.findLastPostId(groupDiscussionInfo.getId())); groupSub.setId(groupDiscussionInfo.getId()); groupSub.setTitle(groupDiscussionInfo.getTitle()); } return groupSubRepository.save(groupSub); } @Override public GroupSub save(GroupSub groupSub) { return groupSubRepository.save(groupSub); } @Override public Optional findById(Integer id) { return groupSubRepository.findById(id); } @Override public List findAll() { return groupSubRepository.findAll(); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/SendBotMessageService.java ================================================ package com.github.javarushcommunity.jrtb.service; import java.util.List; /** * Service for sending messages via telegram-bot. */ public interface SendBotMessageService { /** * Send message via telegram bot. * * @param chatId provided chatId in which would be sent. * @param message provided message to be sent. */ void sendMessage(Long chatId, String message); /** * Send messages via telegram bot. * * @param chatId provided chatId in which would be sent. * @param message collection of provided messages to be sent. */ void sendMessage(Long chatId, List message); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/SendBotMessageServiceImpl.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.springframework.util.CollectionUtils.isEmpty; /** * Implementation of {@link SendBotMessageService} interface. */ @Service public class SendBotMessageServiceImpl implements SendBotMessageService { private final JavarushTelegramBot javarushBot; @Autowired public SendBotMessageServiceImpl(JavarushTelegramBot javarushBot) { this.javarushBot = javarushBot; } @Override public void sendMessage(Long chatId, String message) { if (isBlank(message)) return; SendMessage sendMessage = new SendMessage(); sendMessage.setChatId(chatId.toString()); sendMessage.enableHtml(true); sendMessage.setText(message); try { javarushBot.execute(sendMessage); } catch (TelegramApiException e) { //todo add logging to the project. e.printStackTrace(); } } @Override public void sendMessage(Long chatId, List messages) { if (isEmpty(messages)) return; messages.forEach(m -> sendMessage(chatId, m)); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/StatisticsService.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.dto.StatisticDTO; /** * Service for getting bot statistics. */ public interface StatisticsService { StatisticDTO countBotStatistic(); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/StatisticsServiceImpl.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.dto.GroupStatDTO; import com.github.javarushcommunity.jrtb.dto.StatisticDTO; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; import static org.springframework.util.CollectionUtils.isEmpty; @Service public class StatisticsServiceImpl implements StatisticsService { private final GroupSubService groupSubService; private final TelegramUserService telegramUserService; public StatisticsServiceImpl(GroupSubService groupSubService, TelegramUserService telegramUserService) { this.groupSubService = groupSubService; this.telegramUserService = telegramUserService; } @Override public StatisticDTO countBotStatistic() { List groupStatDTOS = groupSubService.findAll().stream() .filter(it -> !isEmpty(it.getUsers())) .map(groupSub -> new GroupStatDTO(groupSub.getId(), groupSub.getTitle(), groupSub.getUsers().size())) .collect(Collectors.toList()); List allInActiveUsers = telegramUserService.findAllInActiveUsers(); List allActiveUsers = telegramUserService.findAllActiveUsers(); double groupsPerUser = getGroupsPerUser(allActiveUsers); return new StatisticDTO(allActiveUsers.size(), allInActiveUsers.size(), groupStatDTOS, groupsPerUser); } private double getGroupsPerUser(List allActiveUsers) { return (double) allActiveUsers.stream().mapToInt(it -> it.getGroupSubs().size()).sum() / allActiveUsers.size(); } } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/TelegramUserService.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; /** * {@link Service} for handling {@link TelegramUser} entity. */ public interface TelegramUserService { /** * Save provided {@link TelegramUser} entity. * * @param telegramUser provided telegram user. */ void save(TelegramUser telegramUser); /** * Find all active {@link TelegramUser}. * * @return the collection of the active {@link TelegramUser} objects. */ List findAllActiveUsers(); /** * Find all inactive {@link TelegramUser} * * @return the collection of the inactive {@link TelegramUser} objects. */ List findAllInActiveUsers(); /** * Find {@link TelegramUser} by chatId. * * @param chatId provided Chat ID * @return {@link TelegramUser} with provided chat ID or null otherwise. */ Optional findByChatId(Long chatId); } ================================================ FILE: src/main/java/com/github/javarushcommunity/jrtb/service/TelegramUserServiceImpl.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.repository.TelegramUserRepository; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; /** * Implementation of {@link TelegramUserService}. */ @Service public class TelegramUserServiceImpl implements TelegramUserService { private final TelegramUserRepository telegramUserRepository; @Autowired public TelegramUserServiceImpl(TelegramUserRepository telegramUserRepository) { this.telegramUserRepository = telegramUserRepository; } @Override public void save(TelegramUser telegramUser) { telegramUserRepository.save(telegramUser); } @Override public List findAllActiveUsers() { return telegramUserRepository.findAllByActiveTrue(); } @Override public List findAllInActiveUsers() { return telegramUserRepository.findAllByActiveFalse(); } @Override public Optional findByChatId(Long chatId) { return telegramUserRepository.findById(chatId); } } ================================================ FILE: src/main/resources/application-test.properties ================================================ # MySQL configurations: spring.datasource.url=jdbc:mysql://localhost:3306/dev_jrtb_db?characterEncoding=UTF-8 spring.datasource.username=dev_jrtb_db_user spring.datasource.password=dev_jrtb_db_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # TelegramBot configurations: bot.username=tes bot.token=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso ================================================ FILE: src/main/resources/application.properties ================================================ # MySQL configurations: spring.datasource.url=jdbc:mysql://jrtb-db:3306/jrtb_db?characterEncoding=UTF-8 spring.datasource.username=jrtb_db_user spring.datasource.password=jrtb_db_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver javarush.api.path=https://javarush.ru/api/1.0/rest # TelegramBot configurations: bot.username=test.javarush_community_bot bot.token=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso bot.recountNewPostFixedRate = 900000 bot.admins: robeskman,romankh3 ================================================ FILE: src/main/resources/db/migration/V00001__created_tg_user_table.sql ================================================ -- ensure that the table with this name is removed before creating a new one. DROP TABLE IF EXISTS tg_user; -- Create tg_user table CREATE TABLE tg_user ( chat_id VARCHAR(100), active BOOLEAN ); ================================================ FILE: src/main/resources/db/migration/V00002__created_groupsub_many_to_many.sql ================================================ -- add PRIMARY KEY FOR tg_user ALTER TABLE tg_user ADD PRIMARY KEY (chat_id); -- ensure that the tables with these names are removed before creating a new one. DROP TABLE IF EXISTS group_sub; DROP TABLE IF EXISTS group_x_user; CREATE TABLE group_sub ( id INT, title VARCHAR(100), last_article_id INT, PRIMARY KEY (id) ); CREATE TABLE group_x_user ( group_sub_id INT NOT NULL, user_id VARCHAR(100) NOT NULL, FOREIGN KEY (user_id) REFERENCES tg_user(chat_id), FOREIGN KEY (group_sub_id) REFERENCES group_sub(id), UNIQUE(user_id, group_sub_id) ); ================================================ FILE: src/main/resources/db/migration/V00003__rename_last_article_id.sql ================================================ ALTER TABLE group_sub CHANGE COLUMN last_article_id last_post_id INT; ================================================ FILE: src/main/resources/db/migration/V00004_change_chat_Id_type_to_Long.sql ================================================ ALTER TABLE tg_user MODIFY chat_id INT; ================================================ FILE: src/main/resources/log4j2.xml ================================================ ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/AbstractCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.SendBotMessageServiceImpl; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; /** * Abstract class for testing {@link Command}s. */ abstract class AbstractCommandTest { protected JavarushTelegramBot javarushBot = Mockito.mock(JavarushTelegramBot.class); protected SendBotMessageService sendBotMessageService = new SendBotMessageServiceImpl(javarushBot); protected TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class); abstract String getCommandName(); abstract String getCommandMessage(); abstract Command getCommand(); @Test public void shouldProperlyExecuteCommand() throws TelegramApiException { //given Long chatId = 1234567824356L; Update update = prepareUpdate(chatId, getCommandName()); SendMessage sendMessage = new SendMessage(); sendMessage.setChatId(chatId.toString()); sendMessage.setText(getCommandMessage()); sendMessage.enableHtml(true); //when getCommand().execute(update); //then Mockito.verify(javarushBot).execute(sendMessage); } public static Update prepareUpdate(Long chatId, String commandName) { Update update = new Update(); Message message = Mockito.mock(Message.class); Mockito.when(message.getChatId()).thenReturn(chatId); Mockito.when(message.getText()).thenReturn(commandName); update.setMessage(message); return update; } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/AdminHelpCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.AdminHelpCommand.ADMIN_HELP_MESSAGE; import static com.github.javarushcommunity.jrtb.command.CommandName.ADMIN_HELP; @DisplayName("Unit-level testing for AdminHelpCommand") public class AdminHelpCommandTest extends AbstractCommandTest { @Override String getCommandName() { return ADMIN_HELP.getCommandName(); } @Override String getCommandMessage() { return ADMIN_HELP_MESSAGE; } @Override Command getCommand() { return new AdminHelpCommand(sendBotMessageService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/CommandContainerTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.StatisticsService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Arrays; import static java.util.Collections.singletonList; @DisplayName("Unit-level testing for CommandContainer") class CommandContainerTest { private CommandContainer commandContainer; @BeforeEach public void init() { SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class); TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class); JavaRushGroupClient groupClient = Mockito.mock(JavaRushGroupClient.class); GroupSubService groupSubService = Mockito.mock(GroupSubService.class); StatisticsService statisticsService = Mockito.mock(StatisticsService.class); commandContainer = new CommandContainer(sendBotMessageService, telegramUserService, groupClient, groupSubService, singletonList("username"), statisticsService); } @Test public void shouldGetAllTheExistingCommands() { //when-then Arrays.stream(CommandName.values()) .forEach(commandName -> { Command command = commandContainer.findCommand(commandName.getCommandName(), "username"); Assertions.assertNotEquals(UnknownCommand.class, command.getClass()); }); } @Test public void shouldReturnUnknownCommand() { //given String unknownCommand = "/fgjhdfgdfg"; //when Command command = commandContainer.findCommand(unknownCommand, "username"); //then Assertions.assertEquals(UnknownCommand.class, command.getClass()); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/DeleteGroupSubCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.ArrayList; import java.util.Optional; import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate; import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB; import static java.util.Collections.singletonList; @DisplayName("Unit-level testing for DeleteGroupSubCommand") class DeleteGroupSubCommandTest { private Command command; private SendBotMessageService sendBotMessageService; GroupSubService groupSubService; TelegramUserService telegramUserService; @BeforeEach public void init() { sendBotMessageService = Mockito.mock(SendBotMessageService.class); groupSubService = Mockito.mock(GroupSubService.class); telegramUserService = Mockito.mock(TelegramUserService.class); command = new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService); } @Test public void shouldProperlyReturnEmptySubscriptionList() { //given Long chatId = 23456L; Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName()); Mockito.when(telegramUserService.findByChatId(chatId)) .thenReturn(Optional.of(new TelegramUser())); String expectedMessage = "Пока нет подписок на группы. Чтобы добавить подписку напиши /addGroupSub"; //when command.execute(update); //then Mockito.verify(sendBotMessageService).sendMessage(chatId, expectedMessage); } @Test public void shouldProperlyReturnSubscriptionLit() { //given Long chatId = 23456L; Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName()); TelegramUser telegramUser = new TelegramUser(); GroupSub gs1 = new GroupSub(); gs1.setId(123); gs1.setTitle("GS1 Title"); telegramUser.setGroupSubs(singletonList(gs1)); Mockito.when(telegramUserService.findByChatId(chatId)) .thenReturn(Optional.of(telegramUser)); String expectedMessage = "Чтобы удалить подписку на группу - передай комадну вместе с ID группы. \n" + "Например: /deleteGroupSub 16 \n\n" + "я подготовил список всех групп, на которые ты подписан) \n\n" + "имя группы - ID группы \n\n" + "GS1 Title - 123 \n"; //when command.execute(update); //then Mockito.verify(sendBotMessageService).sendMessage(chatId, expectedMessage); } @Test public void shouldRejectByInvalidGroupId() { //given Long chatId = 23456L; Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), "groupSubId")); TelegramUser telegramUser = new TelegramUser(); GroupSub gs1 = new GroupSub(); gs1.setId(123); gs1.setTitle("GS1 Title"); telegramUser.setGroupSubs(singletonList(gs1)); Mockito.when(telegramUserService.findByChatId(chatId)) .thenReturn(Optional.of(telegramUser)); String expectedMessage = "неправильный формат ID группы.\n " + "ID должно быть целым положительным числом"; //when command.execute(update); //then Mockito.verify(sendBotMessageService).sendMessage(chatId, expectedMessage); } @Test public void shouldProperlyDeleteByGroupId() { //given /// prepare update object Long chatId = 23456L; Integer groupId = 1234; Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId)); GroupSub gs1 = new GroupSub(); gs1.setId(123); gs1.setTitle("GS1 Title"); TelegramUser telegramUser = new TelegramUser(); telegramUser.setChatId(chatId); telegramUser.setGroupSubs(singletonList(gs1)); ArrayList users = new ArrayList<>(); users.add(telegramUser); gs1.setUsers(users); Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.of(gs1)); Mockito.when(telegramUserService.findByChatId(chatId)) .thenReturn(Optional.of(telegramUser)); String expectedMessage = "Удалил подписку на группу: GS1 Title"; //when command.execute(update); //then users.remove(telegramUser); Mockito.verify(groupSubService).save(gs1); Mockito.verify(sendBotMessageService).sendMessage(chatId, expectedMessage); } @Test public void shouldDoesNotExistByGroupId() { //given Long chatId = 23456L; Integer groupId = 1234; Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId)); Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.empty()); String expectedMessage = "Не нашел такой группы =/"; //when command.execute(update); //then Mockito.verify(groupSubService).findById(groupId); Mockito.verify(sendBotMessageService).sendMessage(chatId, expectedMessage); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/HelpCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.CommandName.HELP; import static com.github.javarushcommunity.jrtb.command.HelpCommand.HELP_MESSAGE; @DisplayName("Unit-level testing for HelpCommand") public class HelpCommandTest extends AbstractCommandTest { @Override String getCommandName() { return HELP.getCommandName(); } @Override String getCommandMessage() { return HELP_MESSAGE; } @Override Command getCommand() { return new HelpCommand(sendBotMessageService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/ListGroupSubCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate; import static com.github.javarushcommunity.jrtb.command.CommandName.LIST_GROUP_SUB; @DisplayName("Unit-level testing for ListGroupSubCommand") public class ListGroupSubCommandTest { @Test public void shouldProperlyShowsListGroupSub() { //given TelegramUser telegramUser = new TelegramUser(); telegramUser.setActive(true); telegramUser.setChatId(1L); List groupSubList = new ArrayList<>(); groupSubList.add(populateGroupSub(1, "gs1")); groupSubList.add(populateGroupSub(2, "gs2")); groupSubList.add(populateGroupSub(3, "gs3")); groupSubList.add(populateGroupSub(4, "gs4")); telegramUser.setGroupSubs(groupSubList); SendBotMessageService sendBotMessageService = Mockito.mock(SendBotMessageService.class); TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class); Mockito.when(telegramUserService.findByChatId(telegramUser.getChatId())).thenReturn(Optional.of(telegramUser)); ListGroupSubCommand command = new ListGroupSubCommand(sendBotMessageService, telegramUserService); Update update = prepareUpdate(Long.valueOf(telegramUser.getChatId()), LIST_GROUP_SUB.getCommandName()); String joinedGroups = telegramUser.getGroupSubs().stream() .map(it -> "Группа: " + it.getTitle() + " , ID = " + it.getId() + " \n") .collect(Collectors.joining()); String collectedGroups = String.format("Я нашел все подписки на группы: \n\n %s", joinedGroups); //when command.execute(update); //then Mockito.verify(sendBotMessageService).sendMessage(telegramUser.getChatId(), collectedGroups); } private GroupSub populateGroupSub(Integer id, String title) { GroupSub gs = new GroupSub(); gs.setId(id); gs.setTitle(title); return gs; } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/NoCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.CommandName.NO; import static com.github.javarushcommunity.jrtb.command.NoCommand.NO_MESSAGE; @DisplayName("Unit-level testing for NoCommand") public class NoCommandTest extends AbstractCommandTest { @Override String getCommandName() { return NO.getCommandName(); } @Override String getCommandMessage() { return NO_MESSAGE; } @Override Command getCommand() { return new NoCommand(sendBotMessageService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/StartCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.CommandName.START; import static com.github.javarushcommunity.jrtb.command.StartCommand.START_MESSAGE; @DisplayName("Unit-level testing for StartCommand") class StartCommandTest extends AbstractCommandTest { @Override String getCommandName() { return START.getCommandName(); } @Override String getCommandMessage() { return START_MESSAGE; } @Override Command getCommand() { return new StartCommand(sendBotMessageService, telegramUserService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/StatCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.dto.GroupStatDTO; import com.github.javarushcommunity.jrtb.dto.StatisticDTO; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.StatisticsService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Collections; import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate; import static com.github.javarushcommunity.jrtb.command.StatCommand.STAT_MESSAGE; import static java.lang.String.format; @DisplayName("Unit-level testing for StatCommand") public class StatCommandTest { private SendBotMessageService sendBotMessageService; private StatisticsService statisticsService; private Command statCommand; @BeforeEach public void init() { sendBotMessageService = Mockito.mock(SendBotMessageService.class); statisticsService = Mockito.mock(StatisticsService.class); statCommand = new StatCommand(sendBotMessageService, statisticsService); } @Test public void shouldProperlySendMessage() { //given Long chatId = 1234567L; GroupStatDTO groupDto = new GroupStatDTO(1, "group", 1); StatisticDTO statisticDTO = new StatisticDTO(1, 1, Collections.singletonList(groupDto), 2.5); Mockito.when(statisticsService.countBotStatistic()) .thenReturn(statisticDTO); //when statCommand.execute(prepareUpdate(chatId, CommandName.STAT.getCommandName())); //then Mockito.verify(sendBotMessageService).sendMessage(chatId, format(STAT_MESSAGE, statisticDTO.getActiveUserCount(), statisticDTO.getInactiveUserCount(), statisticDTO.getAverageGroupCountByUser(), format("%s (id = %s) - %s подписчиков", groupDto.getTitle(), groupDto.getId(), groupDto.getActiveUserCount()))); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/StopCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.CommandName.STOP; import static com.github.javarushcommunity.jrtb.command.StopCommand.STOP_MESSAGE; @DisplayName("Unit-level testing for StopCommand") public class StopCommandTest extends AbstractCommandTest { @Override String getCommandName() { return STOP.getCommandName(); } @Override String getCommandMessage() { return STOP_MESSAGE; } @Override Command getCommand() { return new StopCommand(sendBotMessageService, telegramUserService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/command/UnknownCommandTest.java ================================================ package com.github.javarushcommunity.jrtb.command; import org.junit.jupiter.api.DisplayName; import static com.github.javarushcommunity.jrtb.command.UnknownCommand.UNKNOWN_MESSAGE; @DisplayName("Unit-level testing for UnknownCommand") public class UnknownCommandTest extends AbstractCommandTest { @Override String getCommandName() { return "/fdgdfgdfgdbd"; } @Override String getCommandMessage() { return UNKNOWN_MESSAGE; } @Override Command getCommand() { return new UnknownCommand(sendBotMessageService); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushGroupClientTest.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; import static com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfoType.TECH; @DisplayName("Integration-level testing for JavaRushGroupClientImplTest") class JavaRushGroupClientTest { public static final String JAVARUSH_API_PATH = "https://javarush.ru/api/1.0/rest"; private final JavaRushGroupClient groupClient = new JavaRushGroupClientImpl(JAVARUSH_API_PATH); @Test public void shouldProperlyGetGroupsWithEmptyArgs() { //given GroupRequestArgs args = GroupRequestArgs.builder().build(); //when List groupList = groupClient.getGroupList(args); //then Assertions.assertNotNull(groupList); Assertions.assertFalse(groupList.isEmpty()); } @Test public void shouldProperlyGetWithOffSetAndLimit() { //given GroupRequestArgs args = GroupRequestArgs.builder() .offset(1) .limit(3) .build(); //when List groupList = groupClient.getGroupList(args); //then Assertions.assertNotNull(groupList); Assertions.assertEquals(3, groupList.size()); } @Test public void shouldProperlyGetGroupsDiscWithEmptyArgs() { //given GroupRequestArgs args = GroupRequestArgs.builder().build(); //when List groupList = groupClient.getGroupDiscussionList(args); //then Assertions.assertNotNull(groupList); Assertions.assertFalse(groupList.isEmpty()); } @Test public void shouldProperlyGetGroupDiscWithOffSetAndLimit() { //given GroupRequestArgs args = GroupRequestArgs.builder() .offset(1) .limit(3) .build(); //when List groupList = groupClient.getGroupDiscussionList(args); //then Assertions.assertNotNull(groupList); Assertions.assertEquals(3, groupList.size()); } @Test public void shouldProperlyGetGroupCount() { //given GroupsCountRequestArgs args = GroupsCountRequestArgs.builder().build(); //when Integer groupCount = groupClient.getGroupCount(args); //then Assertions.assertEquals(30, groupCount); } @Test public void shouldProperlyGetGroupTECHCount() { //given GroupsCountRequestArgs args = GroupsCountRequestArgs.builder() .type(TECH) .build(); //when Integer groupCount = groupClient.getGroupCount(args); //then Assertions.assertEquals(7, groupCount); } @Test public void shouldProperlyGetGroupById() { //given Integer androidGroupId = 16; //when GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId); //then Assertions.assertNotNull(groupById); Assertions.assertEquals(16, groupById.getId()); Assertions.assertEquals(TECH, groupById.getType()); Assertions.assertEquals("android", groupById.getKey()); } @Test public void shouldNotProperlyGetGroupById() { //given Integer androidGroupId = Integer.MAX_VALUE; //when GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId); //then Assertions.assertNull(groupById.getKey()); Assertions.assertNull(groupById.getId()); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/javarushclient/JavaRushPostClientTest.java ================================================ package com.github.javarushcommunity.jrtb.javarushclient; import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; import static com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClientTest.JAVARUSH_API_PATH; @DisplayName("Integration-level testing for JavaRushPostClient") class JavaRushPostClientTest { private final JavaRushPostClient postClient = new JavaRushPostClientImpl(JAVARUSH_API_PATH); @Test public void shouldProperlyGetNew15Posts() { //when List newPosts = postClient.findNewPosts(30, 2935); //then Assertions.assertEquals(15, newPosts.size()); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/repository/GroupSubRepositoryIT.java ================================================ package com.github.javarushcommunity.jrtb.repository; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import java.util.List; import java.util.Optional; import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE; /** * Integration-level testing for {@link GroupSubRepository}. */ @ActiveProfiles("test") @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) public class GroupSubRepositoryIT { @Autowired private GroupSubRepository groupSubRepository; @Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveUsersForGroupSub.sql"}) @Test public void shouldProperlyGetAllUsersForGroupSub() { //when Optional groupSubFromDB = groupSubRepository.findById(1); //then Assertions.assertTrue(groupSubFromDB.isPresent()); Assertions.assertEquals(1, groupSubFromDB.get().getId()); List users = groupSubFromDB.get().getUsers(); for(int i=0; i users = telegramUserRepository.findAllByActiveTrue(); //then Assertions.assertEquals(5, users.size()); } @Sql(scripts = {"/sql/clearDbs.sql"}) @Test public void shouldProperlySaveTelegramUser() { //given TelegramUser telegramUser = new TelegramUser(); telegramUser.setChatId(1234567890L); telegramUser.setActive(false); telegramUserRepository.save(telegramUser); //when Optional saved = telegramUserRepository.findById(telegramUser.getChatId()); //then Assertions.assertTrue(saved.isPresent()); Assertions.assertEquals(telegramUser, saved.get()); } @Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveGroupSubsForUser.sql"}) @Test public void shouldProperlyGetAllGroupSubsForUser() { //when Optional userFromDB = telegramUserRepository.findById(1L); //then Assertions.assertTrue(userFromDB.isPresent()); List groupSubs = userFromDB.get().getGroupSubs(); for (int i = 0; i < groupSubs.size(); i++) { Assertions.assertEquals(String.format("g%s", (i + 1)), groupSubs.get(i).getTitle()); Assertions.assertEquals(i + 1, groupSubs.get(i).getId()); Assertions.assertEquals(i + 1, groupSubs.get(i).getLastPostId()); } } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/service/GroupSubServiceTest.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient; import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo; import com.github.javarushcommunity.jrtb.repository.GroupSubRepository; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Optional; @DisplayName("Unit-level testing for GroupSubService") public class GroupSubServiceTest { private GroupSubService groupSubService; private GroupSubRepository groupSubRepository; private JavaRushGroupClient javaRushGroupClient; private TelegramUser newUser; private final static Long CHAT_ID = 1234234L; private final static Integer GROUP_ID = 1123; private final static Integer LAST_POST_ID = 310; @BeforeEach public void init() { TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class); groupSubRepository = Mockito.mock(GroupSubRepository.class); javaRushGroupClient = Mockito.mock(JavaRushGroupClient.class); groupSubService = new GroupSubServiceImpl(groupSubRepository, telegramUserService, javaRushGroupClient); newUser = new TelegramUser(); newUser.setActive(true); newUser.setChatId(CHAT_ID); Mockito.when(telegramUserService.findByChatId(CHAT_ID)).thenReturn(Optional.of(newUser)); Mockito.when(javaRushGroupClient.findLastPostId(GROUP_ID)).thenReturn(LAST_POST_ID); } @Test public void shouldProperlySaveGroup() { //given GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo(); groupDiscussionInfo.setId(GROUP_ID); groupDiscussionInfo.setTitle("g1"); GroupSub expectedGroupSub = new GroupSub(); expectedGroupSub.setId(groupDiscussionInfo.getId()); expectedGroupSub.setTitle(groupDiscussionInfo.getTitle()); expectedGroupSub.setLastPostId(LAST_POST_ID); expectedGroupSub.addUser(newUser); //when groupSubService.save(CHAT_ID, groupDiscussionInfo); //then Mockito.verify(groupSubRepository).save(expectedGroupSub); } @Test public void shouldProperlyAddUserToExistingGroup() { //given TelegramUser oldTelegramUser = new TelegramUser(); oldTelegramUser.setChatId(2L); oldTelegramUser.setActive(true); GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo(); groupDiscussionInfo.setId(1); groupDiscussionInfo.setTitle("g1"); GroupSub groupFromDB = new GroupSub(); groupFromDB.setId(groupDiscussionInfo.getId()); groupFromDB.setTitle(groupDiscussionInfo.getTitle()); groupFromDB.addUser(oldTelegramUser); Mockito.when(groupSubRepository.findById(groupDiscussionInfo.getId())).thenReturn(Optional.of(groupFromDB)); GroupSub expectedGroupSub = new GroupSub(); expectedGroupSub.setId(groupDiscussionInfo.getId()); expectedGroupSub.setTitle(groupDiscussionInfo.getTitle()); expectedGroupSub.addUser(oldTelegramUser); expectedGroupSub.addUser(newUser); //when groupSubService.save(CHAT_ID, groupDiscussionInfo); //then Mockito.verify(groupSubRepository).findById(groupDiscussionInfo.getId()); Mockito.verify(groupSubRepository).save(expectedGroupSub); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/service/SendBotMessageServiceTest.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.bot.JavarushTelegramBot; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; @DisplayName("Unit-level testing for SendBotMessageService") public class SendBotMessageServiceTest { private SendBotMessageService sendBotMessageService; private JavarushTelegramBot javarushBot; @BeforeEach public void init() { javarushBot = Mockito.mock(JavarushTelegramBot.class); sendBotMessageService = new SendBotMessageServiceImpl(javarushBot); } @Test public void shouldProperlySendMessage() throws TelegramApiException { //given Long chatId = 123L; String message = "test_message"; SendMessage sendMessage = new SendMessage(); sendMessage.setText(message); sendMessage.setChatId(chatId.toString()); sendMessage.enableHtml(true); //when sendBotMessageService.sendMessage(chatId, message); //then Mockito.verify(javarushBot).execute(sendMessage); } } ================================================ FILE: src/test/java/com/github/javarushcommunity/jrtb/service/StatisticsServiceImplTest.java ================================================ package com.github.javarushcommunity.jrtb.service; import com.github.javarushcommunity.jrtb.dto.GroupStatDTO; import com.github.javarushcommunity.jrtb.dto.StatisticDTO; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static java.util.Collections.singletonList; @DisplayName("Unit-level testing for StatisticsService") class StatisticsServiceTest { private GroupSubService groupSubService; private TelegramUserService telegramUserService; private StatisticsService statisticsService; @BeforeEach public void init() { groupSubService = Mockito.mock(GroupSubService.class); telegramUserService = Mockito.mock(TelegramUserService.class); statisticsService = new StatisticsServiceImpl(groupSubService, telegramUserService); } @Test public void shouldProperlySendStatDTO() { //given Mockito.when(telegramUserService.findAllInActiveUsers()).thenReturn(singletonList(new TelegramUser())); TelegramUser activeUser = new TelegramUser(); activeUser.setGroupSubs(singletonList(new GroupSub())); Mockito.when(telegramUserService.findAllActiveUsers()).thenReturn(singletonList(activeUser)); GroupSub groupSub = new GroupSub(); groupSub.setTitle("group"); groupSub.setId(1); groupSub.setUsers(singletonList(new TelegramUser())); Mockito.when(groupSubService.findAll()).thenReturn(singletonList(groupSub)); //when StatisticDTO statisticDTO = statisticsService.countBotStatistic(); //then Assertions.assertNotNull(statisticDTO); Assertions.assertEquals(1, statisticDTO.getActiveUserCount()); Assertions.assertEquals(1, statisticDTO.getInactiveUserCount()); Assertions.assertEquals(1.0, statisticDTO.getAverageGroupCountByUser()); Assertions.assertEquals(singletonList(new GroupStatDTO(groupSub.getId(), groupSub.getTitle(), groupSub.getUsers().size())), statisticDTO.getGroupStatDTOs()); } } ================================================ FILE: src/test/resources/sql/clearDbs.sql ================================================ DELETE FROM group_x_user; DELETE FROM group_sub; DELETE FROM tg_user; ================================================ FILE: src/test/resources/sql/fiveGroupSubsForUser.sql ================================================ INSERT INTO tg_user VALUES (1, 1); INSERT INTO group_sub VALUES (1, 'g1', 1), (2, 'g2', 2), (3, 'g3', 3), (4, 'g4', 4), (5, 'g5', 5); INSERT INTO group_x_user VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 1); ================================================ FILE: src/test/resources/sql/fiveUsersForGroupSub.sql ================================================ INSERT INTO tg_user VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 1); INSERT INTO group_sub VALUES (1, 'g1', 1); INSERT INTO group_x_user VALUES (1, 1), (1, 2), (1, 3), (1, 4), (1, 5); ================================================ FILE: src/test/resources/sql/telegram_users.sql ================================================ INSERT INTO tg_user VALUES ("123456789", 1); INSERT INTO tg_user VALUES ("123456788", 1); INSERT INTO tg_user VALUES ("123456787", 1); INSERT INTO tg_user VALUES ("123456786", 1); INSERT INTO tg_user VALUES ("123456785", 1); INSERT INTO tg_user VALUES ("123456784", 0); INSERT INTO tg_user VALUES ("123456782", 0); INSERT INTO tg_user VALUES ("123456781", 0); ================================================ FILE: start.sh ================================================ #!/bin/bash # Pull new changes git pull # Prepare Jar mvn clean mvn package # Ensure, that docker-compose stopped docker-compose stop # Add environment variables export BOT_NAME=$1 export BOT_TOKEN=$2 export BOT_DB_USERNAME='prod_jrtb_db_user' export BOT_DB_PASSWORD='Pap9L9VVUkNYj99GCUCC3mJkb' # Start new deployment docker-compose up --build -d ================================================ FILE: stop.sh ================================================ #!/bin/bash # Ensure, that docker-compose stopped docker-compose stop # Ensure, that the old application won't be deployed again. mvn clean