JavaRush /Java Blog /Random EN /Adding a client to articles - "Java project from A to Z"

Adding a client to articles - "Java project from A to Z"

Published in the Random EN group
Hello everyone, my dear friends. Step by step, we are getting closer to our goal - to enter the MVP of our project - CodeGym Telegram Bot. As I said in the last article, there are only 5 tasks left. Today we will cover two of them. "Java project from A to Z": Adding a client to articles - 1I want to reiterate that the project will not end there. I still have a lot of ideas and visions of how this project should develop, that new things can be added to it, what can be done better. Before MVP, let's make another separate article on the topic of refactoring - that is, about improving the quality of the code without changing its functionality. By that time, the whole project will already be visible and it will be clear what and where can be improved. In our case, we will be as protected as possible from not breaking the functionality, because a lot of tests have been written. We will also write a retrospective on what we wanted and what we got in the end. This is a very useful thing: let's see how correctly we saw each other six months ago. At least I'm very interested in it. If someone has a desire to try himself as a manual tester - write, we will cooperate. Let's make this project better together! So, here they are: two tasks described six months ago:JRTB-8 and JRTB-9 . I started to look at what needs to be implemented for these tasks, and I realized that everything was ready in terms of launching commands. It happens…) Here, you can see in StartCommand , the execute method :
@Override
public void execute(Update update) {
   String chatId = update.getMessage().getChatId().toString();

   telegramUserService.findByChatId(chatId).ifPresentOrElse(
           user -> {
               user.setActive(true);
               telegramUserService.save(user);
           },
           () -> {
               TelegramUser telegramUser = new TelegramUser();
               telegramUser.setActive(true);
               telegramUser.setChatId(chatId);
               telegramUserService.save(telegramUser);
           });

   sendBotMessageService.sendMessage(chatId, START_MESSAGE);
}
The logic here is that if our database already has such a user by chatId, we simply set the active = true field to him. And if there is no such user, we create a new one. Likewise for the /stop command in StopCommand :
@Override
public void execute(Update update) {
   telegramUserService.findByChatId(update.getMessage().getChatId().toString())
           .ifPresent(it -> {
               it.setActive(false);
               telegramUserService.save(it);
           });
   sendBotMessageService.sendMessage(update.getMessage().getChatId().toString(), STOP_MESSAGE);
}
It can be seen that when this command is called, only the active = false field is set for the user. And that's all: his subscriptions will live and wait in the wings, when the user again decides to activate the chat with the bot. And it would seem that the task has already been done and it can be closed. But it was not there. The most important task is to create an alert about new articles in the subscription. This is exactly where these tasks will be completely updated and done. That is, until we have implemented notification of new articles, it cannot be closed. Therefore, we will deal with the task of JRTB-4 - creating a check every 20 minutes and notification of new articles. Friends! Do you want to know immediately when a new project code is released? When is the new article coming out? Join my tg channel . There I collect my articles, my thoughts, my open-source development together.

Implementing JRTB-4

What we need to do in this task:
  1. Create a job that will periodically go to all groups to which we have a database subscription, sort articles by publication date and check if the last publication ID matches the value in GroupSub. If it does not match, then you need to understand exactly how many articles have been published since the last time. Update last_article_id in GroupSub7 to the current state.

  2. When we have found the list of published articles, we find all ACTIVE users for these groups and send them notifications about new articles.

To do this, we will use such a thing as the Spring Scheduler. This is a mechanism in the Spring Framework, with which you can create tasks that will be executed at a specific time. Either every 15-20-40 minutes, or every Thursday at 15:30 or some other option. They are also called tracing paper from English - job. While we will be doing this task, I will deliberately leave one defect in the search for new articles. It is rather rare and appeared only in a situation where I manually tested the operation of this task. To do this, you need to write a client to search for articles. To do this, we will use the already familiar Swagger API . There is a post-controller. We are only interested in searching for a collection of articles by certain filters:
/api/1.0/rest/posts Get posts by filters
We will work with this request. What do we need in it? Get a list of articles that belong to a specific group, while they must be sorted by publication date. This way we can take the last 15 articles and check if there are any new publications based on lastArticleId from our database. If there are, we will pass them on to be processed and sent to the user. So we need to write a CodeGymPostClient .

Writing CodeGymPostClient

Here we will not try to cover all the requests that were passed to us in the API and will create only the one that we need. By doing this, we achieve two goals at once:
  1. We speed up the process of writing our application.

  2. We leave this work for those who want to help our community and decide to try themselves as a developer. I will do tasks for this that can be completed after the MVP.

Therefore, let's start. To query the Models section in Swagger UI, let's create the following DTOs:"Java project from A to Z": Adding a client to articles - 2

BaseUserInfo:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

import lombok.Data;

/**
* DTO, which represents base user information.
*/
@Data
public class BaseUserInfo {
   private String city;
   private String country;
   private String displayName;
   private Integer id;
   private String job;
   private String key;
   private Integer level;
   private String pictureUrl;
   private String position;
   private UserPublicStatus publicStatus;
   private String publicStatusMessage;
   private Integer rating;
   private Integer userId;
}

language:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents languages.
*/
public enum Language {
   UNKNOWN,
   ENGLISH,
   GERMAN,
   SPANISH,
   HINDI,
   FRENCH,
   PORTUGUESE,
   POLISH,
   BENGALI,
   PUNJABI,
   CHINESE,
   ITALIAN,
   INDONESIAN,
   MARATHI,
   TAMIL,
   TELUGU,
   JAPANESE,
   KOREAN,
   URDU,
   TAIWANESE,
   NETHERLANDS,
   RUSSIAN,
   UKRAINIAN
}

LikesInfo:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents like's information.
*/
public class LikesInfo {

   private Integer count;
   private LikeStatus status;
}

LikeStatus:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents like's status.
*/
public enum LikeStatus {

   UNKNOWN,
   LIKE,
   HOT,
   FOLLOW,
   FAVORITE,
   SOLUTION,
   HELPFUL,
   ARTICLE,
   OSCAR,
   DISLIKE,
   WRONG,
   SPAM,
   ABUSE,
   FOUL,
   TROLLING,
   OFFTOPIC,
   DUPLICATE,
   DIRTY,
   OUTDATED,
   BORING,
   UNCLEAR,
   HARD,
   EASY,
   FAKE,
   SHAM,
   AWFUL
}

posttype:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents post types.
*/
public enum PostType {
   UNKNOWN, USUAL, INNER_LINK, OUTER_LINK
}

UserPublicStatus:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents user public status.
*/
public enum UserPublicStatus {
   UNKNOWN,
   BEGINNER,
   ACTIVE,
   STRONG,
   GRADUATED,
   INTERNSHIP_IN_PROGRESS,
   INTERNSHIP_COMPLETED,
   RESUME_COMPLETED,
   LOOKING_FOR_JOB,
   HAVE_JOB;
}

VisibilityStatus:
package com.github.codegymcommunity.jrtb.codegymclient.dto;

/**
* DTO, which represents visibility status.
*/
public enum VisibilityStatus {
   UNKNOWN,
   RESTRICTED,
   PUBLIC,
   PROTECTED,
   PRIVATE,
   DISABLED,
   DELETED
}
Based on all these DTOs, let's write a main class for getting articles:

post info:

package com.github.codegymcommunity.jrtb.codegymclient.dto;

import lombok.Data;

/**
* DTO, which represents post information.
*/
@Data
public class PostInfo {

   private BaseUserInfo authorInfo;
   private Integer commentsCount;
   private String content;
   private Long createdTime;
   private String description;
   private GroupInfo groupInfo;
   private Integer id;
   private String key;
   private Language language;
   private LikesInfo likesInfo;
   private GroupInfo originalGroupInfo;
   private String pictureUrl;
   private Double rating;
   private Integer ratingCount;
   private String title;
   private PostType type;
   private Long updatedTime;
   private UserDiscussionInfo userDiscussionInfo;
   private Integer views;
   private VisibilityStatus visibilityStatus;

}
Now let's create an interface for work and its implementation. We will need only one method for working with articles:

CodeGymPostClient:

package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;

import java.util.List;

/**
* Client for Javarush Open API corresponds to Posts.
*/
public interface CodeGymPostClient {

   /**
    * Find new posts since lastPostId in provided group.
    *
    * @param groupId provided group ID.
    * @param lastPostId provided last post ID.
    * @return the collection of the new {@link PostInfo}.
    */
   List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId);
}
findNewPosts takes two arguments: the group ID and the last post ID the bot has already posted. Therefore, all those articles that came out later than the article with lastPostId will be transferred . And its implementation:
package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class CodeGymPostClientImpl implements CodeGymPostClient {

   private final String codegymApiPostPath;

   public CodeGymPostClientImpl(@Value("${codegym.api.path}") String codegymApi) {
       this.codegymApiPostPath = codegymApi + "/posts";
   }

   @Override
   public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
       List<PostInfo> lastPostsByGroup = Unirest.get(codegymApiPostPath)
               .queryString("order", "NEW")
               .queryString("groupKid", groupId)
               .queryString("limit", 15)
               .asObject(new GenericType<List<PostInfo>>() {
               }).getBody();
       List<PostInfo> newPosts = new ArrayList<>();
       for (PostInfo post : lastPostsByGroup) {
           if (lastPostId.equals(post.getId())) {
               return newPosts;
           }
           newPosts.add(post);
       }
       return newPosts;
   }
}
We add several filters to the query:
  • order = NEW - so that the list contains new ones first;
  • groupKid = groupId - search only for certain groups;
  • limit = 15 - limit the number of articles per request. We have a frequency of 15-20 minutes and we expect that during this time MORE than 15(!) will be written.
Further, when we have found articles, we run through the list and look for new ones. The algorithm is simple and clear. If you want to improve it - write). Let's write a simple test for this client:
package com.github.codegymcommunity.jrtb.codegymclient;

import com.github.codegymcommunity.jrtb.codegymclient.dto.PostInfo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.codegymcommunity.jrtb.codegymclient.CodeGymGroupClientTest.CODEGYM_API_PATH;

@DisplayName("Integration-level testing for CodeGymPostClient")
class CodeGymPostClientTest {

   private final CodeGymPostClient postClient = new CodeGymPostClientImpl(CODEGYM_API_PATH);

   @Test
   public void shouldProperlyGetNew15Posts() {
       //when
       List<PostInfo> newPosts = postClient.findNewPosts(30, 2935);

       //then
       Assertions.assertEquals(15, newPosts.size());
   }
}
This is a very simple test that checks if there is a connection with the client at all or not. It finds 15 new articles in the Java projects group, because I give it the ID of the first article in this group, and there are already more than 15 of them ... There are already 22 of them! I didn't even think there would be so many of them. How did I find out quickly? Do you think he went to count them? Not-a) I used the swager and looked at the number of articles for a certain group. By the way, this can be seen in others as well... And how many articles are there in the RANDOM group?... Now I will say: there are 1062 of them! Serious amount.

End of the first part

Here we have added work with the client by articles. Everything has already been done, this time, I think everything should go quickly and easily. In the next article , we will add Spring Scheduler and write FindNewArticleService . Well, as usual, like - subscribe - bell , put a star to our project , write comments and rate the article! Thank you all for reading - see you soon!"Java project from A to Z": Adding a client to articles - 3

List of all materials in the series at the beginning of this article.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION