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

Adding a client to the 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 becoming the MVP of our project - JavaRush 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 repeat that the project will not end here. I still have a ton of ideas and visions of how this project should develop, what new things can be added to it, what can be done better. Before MVP, we will make a separate article on the topic of refactoring - that is, about improving the quality of code without changing its functionality. By that time, the entire project will be visible and it will be clear what and where can be improved. In our case, we will be maximally protected from breaking the functionality, because many 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 everything was seen six months ago. At least this is very interesting to me. If anyone would like to try themselves as a manual tester, write to us and we’ll collaborate. Let's make this project better together! So, here they are: two tasks described six months ago: JRTB-8 and JRTB-9 . I started looking at what needed to be implemented for these tasks, and realized that in terms of launching commands, everything was already ready. It happens...) Here, you can look at the 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 works here: if our database already has such a user by chatId, we simply set the active = true field for him. And if there is no such user, we create a new one. Same 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 calling this command, 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 completed and 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 where these tasks will be completely updated and completed. That is, until we have implemented notification of new articles, it cannot be closed. Therefore, let's take care of task JRTB-4 - creating a check every 20 minutes and notifications about new articles. Friends! Do you want to know immediately when new code for the project is released? When does a new article come out? Join my tg channel . There I collect my articles, my thoughts, my open-source development together.

We implement JRTB-4

What we need to do as part of this task:
  1. Create a job that will periodically visit all groups for which we have subscriptions in the database, sort articles by publication date and check whether the ID of the last publication matches the value in GroupSub. If it doesn’t match, then you need to understand exactly how many articles have been published since the last time. We update last_article_id in GroupSub7 to the current state.

  2. When we have found a 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 Spring Scheduler. This is a mechanism in the Spring Framework, with it 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 - joba. While we are doing this task, I will deliberately leave one defect in the search for new articles. It is quite rare and only appeared in a situation where I manually tested the operation of this task. To do this you need to write a client for searching articles. To do this, we will use the Swagger API that is already familiar to us . There is a post-controller. We are only interested in searching for a collection of articles using 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, and they should be sorted by publication date. This way we can take the last 15 articles and check if new publications have been published based on lastArticleId from our database. If there are any, we will pass them on for processing and sending to the user. So we need to write JavaRushPostClient .

We write JavaRushPostClient

Here we will not try to cover all the requests that were passed to us in the API and will create only the one 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 to those who want to help our community and decide to try themselves as a developer. I will make tasks for this that can be completed after the MVP.

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

BaseUserInfo:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

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

Language:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

LikesInfo:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   private Integer count;
   private LikeStatus status;
}

LikeStatus:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

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

PostType:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

UserPublicStatus:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

VisibilityStatus:
package com.github.javarushcommunity.jrtb.javarushclient.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 to receive articles:

PostInfo:

package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;

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

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

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

JavaRushPostClient:

package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;

import java.util.List;

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

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

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

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

@Component
public class JavaRushPostClientImpl implements JavaRushPostClient {

   private final String javarushApiPostPath;

   public JavaRushPostClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiPostPath = javarushApi + "/posts";
   }

   @Override
   public List<PostInfo> findNewPosts(Integer groupId, Integer lastPostId) {
       List<PostInfo> lastPostsByGroup = Unirest.get(javarushApiPostPath)
               .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 request:
  • order = NEW - so that the list contains new ones first;
  • groupKid = groupId - search only for certain groups;
  • limit = 15 — we limit the number of articles per request. Our frequency is 15-20 minutes and we expect that during this time no MORE than 15 (!) will be written.
Next, when we have found articles, we run through the list and look for new ones. The algorithm is simple and intuitive. If you want to improve it, write). Let's write a simple test for this client:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.PostInfo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClientTest.JAVARUSH_API_PATH;

@DisplayName("Integration-level testing for JavaRushPostClient")
class JavaRushPostClientTest {

   private final JavaRushPostClient postClient = new JavaRushPostClientImpl(JAVARUSH_API_PATH);

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

       //then
       Assertions.assertEquals(15, newPosts.size());
   }
}
This is a very simple test that checks whether there is any communication with the client at all or not. He finds 15 new articles in the Java projects group, because I give him 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? Nope) I used a swager and looked at the number of articles for a certain group. By the way, you can look this way in others... And how many articles are in the RANDOM group?... I’ll tell you now: there are 1062 of them! Serious amount.

End of the first part

Here we have added work with the client by article. We’ve already done everything, this time I think everything should be simple and quick. In the next article we will add Spring Scheduler and write FindNewArticleService . Well, as usual, like - subscribe - ring the bell , give our project a star , write comments and rate the article! Thank you all for reading - see you soon!

A list of all materials in the series is at the beginning of this article.

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