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