JavaRush /Blogue Java /Random-PT /Estamos adicionando a capacidade de assinar um grupo de a...
Roman Beekeeper
Nível 35

Estamos adicionando a capacidade de assinar um grupo de artigos. (Parte 2) - "Projeto Java de A a Z"

Publicado no grupo Random-PT
Olá a todos! Continuamos trabalhando na tarefa que iniciamos na semana passada ."Projeto Java de A a Z": Adicionando a capacidade de assinar um grupo de artigos.  Parte 2 - 1

Implementamos JRTB-5

Agora precisamos adicionar um comando para que possamos assinar algum grupo de artigos do JavaRush. Como fazer isso? Seguiremos o cenário mais simples que criei. Como temos acesso por ID de grupo, precisamos que o usuário o transfira. Para isso, o usuário digitará o comando /addGroupSub GROUP_ID, que funcionará de duas maneiras: se vier apenas o comando em si: /addGroupSub , uma lista de todos os grupos e seus IDs será enviada em resposta. Então o usuário poderá selecionar o ID do grupo que necessita e criar a segunda versão da solicitação neste comando: /addGroupSub GROUP_ID - e então haverá um registro deste grupo com este usuário. Acho que podemos fazer melhor no futuro. Nosso objetivo é mostrar o desenvolvimento, e não a experiência superlegal do usuário (tenho vergonha de dizer, mas não conheço o termo em russo que significaria isso). Para adicionar adequadamente a funcionalidade que percorre todo o aplicativo (no nosso caso, do cliente do bot de telegrama ao banco de dados), você precisa começar de algum ponto. Faremos isso do lado do banco de dados.

Adicionando uma nova migração ao banco de dados

A primeira coisa a fazer é adicionar uma nova migração de banco de dados e a capacidade de salvar dados de assinatura de grupos de usuários em JR. Para lembrar como deveria ser, volte ao artigo “ Planejamento de projetos: medir sete vezes ”. Lá na segunda foto há um diagrama aproximado do banco de dados. Precisamos adicionar uma tabela para armazenar informações do grupo:
  • O ID do grupo no JavaRush também será o nosso ID. Confiamos neles e acreditamos que esses IDs são únicos;
  • título - nas nossas fotos era nome - o nome informal do grupo; isto é, o que vemos no site JavaRush;
  • last_article_id - e este é um campo interessante. Ele armazenará o último ID do artigo deste grupo, que o bot já enviou aos seus assinantes. Utilizando este campo, o mecanismo de busca de novos artigos funcionará. Novos assinantes não receberão artigos publicados antes da inscrição do usuário: apenas aqueles que foram publicados após a inscrição no grupo.
Teremos também um relacionamento muitos-para-muitos entre a tabela grupos e usuários, porque cada usuário pode ter muitas assinaturas de grupo (um para muitos), e cada assinatura de grupo pode ter muitos usuários (um para muitos, apenas por outro lado). Acontece que este será o nosso muitos para muitos. Para quem tiver dúvidas, revise os artigos na base de dados. Sim, em breve pretendo criar um post no canal Telegram, onde reunirei todos os artigos da base de dados. Esta é a aparência da nossa segunda migração de banco de dados.
V00002__created_groupsub_many_to_many.sql:

-- add PRIMARY KEY FOR tg_user
ALTER TABLE tg_user ADD PRIMARY KEY (chat_id);

-- ensure that the tables with these names are removed before creating a new one.
DROP TABLE IF EXISTS group_sub;
DROP TABLE IF EXISTS group_x_user;

CREATE TABLE group_sub (
   id INT,
   title VARCHAR(100),
   last_article_id INT,
   PRIMARY KEY (id)
);

CREATE TABLE group_x_user (
   group_sub_id INT NOT NULL,
   user_id VARCHAR(100) NOT NULL,
   FOREIGN KEY (user_id) REFERENCES tg_user(chat_id),
   FOREIGN KEY (group_sub_id) REFERENCES group_sub(id),
   UNIQUE(user_id, group_sub_id)
);
É importante observar que primeiro altero a tabela antiga - adiciono uma chave primária a ela. De alguma forma, senti falta disso na época, mas agora o MySQL não me deu a oportunidade de adicionar uma FOREIGN KEY para a tabela gorup_x_user e, como parte dessa migração, atualizei o banco de dados. Observe um aspecto importante. A alteração do banco de dados deve ser feita exatamente desta forma - tudo o que é necessário está na nova migração, mas não atualizando uma migração já lançada. Sim, no nosso caso nada aconteceria, pois este é um projeto de teste e sabemos que está implantado em apenas um local, mas esta seria a abordagem errada. Mas queremos que tudo dê certo. Em seguida, vem a exclusão de tabelas antes de criá-las. Por que é isso? Para que se por acaso existissem tabelas com tais nomes no banco de dados, a migração não falharia e funcionaria exatamente como esperado. E então adicionamos duas tabelas. Tudo estava como queríamos. Agora precisamos lançar nosso aplicativo. Se tudo começar e não falhar, a migração será registrada. E para verificar isso, vamos ao banco de dados para ter certeza de que: a) tais tabelas apareceram; b) há uma nova entrada na tabela técnica de flyway. Isso conclui o trabalho de migração, vamos passar para os repositórios.

Adicionando uma camada de repositório

Graças ao Spring Boot Data, tudo é muito simples aqui: precisamos adicionar a entidade GroupSub, atualizar ligeiramente o TelegramUser e adicionar um GroupSubRepository quase vazio: Adicionamos a entidade GroupSub ao mesmo pacote que o TelegramUser:
package com.github.javarushcommunity.jrtb.repository.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

import static java.util.Objects.isNull;

@Data
@Entity
@Table(name = "group_sub")
@EqualsAndHashCode
public class GroupSub {

   @Id
   private Integer id;

   @Column(name = "title")
   private String title;

   @Column(name = "last_article_id")
   private Integer lastArticleId;

   @ManyToMany(fetch = FetchType.EAGER)
   @JoinTable(
           name = "group_x_user",
           joinColumns = @JoinColumn(name = "group_sub_id"),
           inverseJoinColumns = @JoinColumn(name = "user_id")
   )
   private List<TelegramUser> users;

   public void addUser(TelegramUser telegramUser) {
       if (isNull(users)) {
           users = new ArrayList<>();
       }
       users.add(telegramUser);
   }
}
Uma coisa que vale a pena notar é que temos um campo de usuários adicional que conterá uma coleção de todos os usuários inscritos no grupo. E duas anotações - ManyToMany e JoinTable - são exatamente o que precisamos para isso. O mesmo campo precisa ser adicionado para TelegramUser:
@ManyToMany(mappedBy = "users", fetch = FetchType.EAGER)
private List<GroupSub> groupSubs;
Este campo usa junções escritas na entidade GroupSub. E, de fato, nossa classe de repositório para GroupSub é GroupSubRepository :
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
* {@link Repository} for {@link GroupSub} entity.
*/
@Repository
public interface GroupSubRepository extends JpaRepository<GroupSub, Integer> {
}
Neste estágio, não precisamos de métodos adicionais: aqueles implementados no ancestral JpaRepository são suficientes para nós. Vamos escrever um teste no TelegramUserRepositoryIT que verificará se nosso muitos para muitos funciona. A ideia do teste é adicionar 5 grupos de assinaturas por usuário ao banco de dados através de um script sql, pegar esse usuário pelo seu ID e verificar se recebemos exatamente esses grupos e com exatamente os mesmos valores. Como fazer isso? Você pode incorporar um contador aos dados, que podemos analisar e verificar. Aqui está o script fiveGroupSubsForUser.sql:
INSERT INTO tg_user VALUES (1, 1);

INSERT INTO group_sub VALUES
(1, 'g1', 1),
(2, 'g2', 2),
(3, 'g3', 3),
(4, 'g4', 4),
(5, 'g5', 5);

INSERT INTO group_x_user VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1);
E o teste em si:
@Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveGroupSubsForUser.sql"})
@Test
public void shouldProperlyGetAllGroupSubsForUser() {
   //when
   Optional<TelegramUser> userFromDB = telegramUserRepository.findById("1");

   //then
   Assertions.assertTrue(userFromDB.isPresent());
   List<GroupSub> groupSubs = userFromDB.get().getGroupSubs();
   for (int i = 0; i < groupSubs.size(); i++) {
       Assertions.assertEquals(String.format("g%s", (i + 1)), groupSubs.get(i).getTitle());
       Assertions.assertEquals(i + 1, groupSubs.get(i).getId());
       Assertions.assertEquals(i + 1, groupSubs.get(i).getLastArticleId());
   }
}
Agora vamos adicionar um teste com o mesmo significado para a entidade GroupSub. Para fazer isso, vamos criar uma classe de teste groupSubRepositoryIT no mesmo pacote que groupSubRepositoryIT :
package com.github.javarushcommunity.jrtb.repository;

import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.util.List;
import java.util.Optional;

import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;

/**
* Integration-level testing for {@link GroupSubRepository}.
*/
@ActiveProfiles("test")
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
public class GroupSubRepositoryIT {

   @Autowired
   private GroupSubRepository groupSubRepository;

   @Sql(scripts = {"/sql/clearDbs.sql", "/sql/fiveUsersForGroupSub.sql"})
   @Test
   public void shouldProperlyGetAllUsersForGroupSub() {
       //when
       Optional<GroupSub> groupSubFromDB = groupSubRepository.findById(1);

       //then
       Assertions.assertTrue(groupSubFromDB.isPresent());
       Assertions.assertEquals(1, groupSubFromDB.get().getId());
       List<TelegramUser> users = groupSubFromDB.get().getUsers();
       for(int i=0; i<users.size(); i++) {
           Assertions.assertEquals(String.valueOf(i + 1), users.get(i).getChatId());
           Assertions.assertTrue(users.get(i).isActive());
       }
   }
}
E o script fiveUsersForGroupSub.sql ausente:
INSERT INTO tg_user VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1);

INSERT INTO group_sub VALUES (1, 'g1', 1);

INSERT INTO group_x_user VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5);
Neste ponto, parte do trabalho com o repositório pode ser considerada concluída. Agora vamos escrever uma camada de serviço.

Nós escrevemos GroupSubService

Nesta fase, para trabalhar com grupos de assinaturas, só precisamos conseguir salvá-los, então não há problema: criamos o serviço GroupSubService e sua implementação do GroupSubServiceImpl em um pacote que contém outros serviços - serviço:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;

/**
* Service for manipulating with {@link GroupSub}.
*/
public interface GroupSubService {

   GroupSub save(String chatId, GroupDiscussionInfo groupDiscussionInfo);
}
E sua implementação:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.GroupSubRepository;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.ws.rs.NotFoundException;
import java.util.Optional;

@Service
public class GroupSubServiceImpl implements GroupSubService {

   private final GroupSubRepository groupSubRepository;
   private final TelegramUserService telegramUserService;

   @Autowired
   public GroupSubServiceImpl(GroupSubRepository groupSubRepository, TelegramUserService telegramUserService) {
       this.groupSubRepository = groupSubRepository;
       this.telegramUserService = telegramUserService;
   }

   @Override
   public GroupSub save(String chatId, GroupDiscussionInfo groupDiscussionInfo) {
       TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new);
       //TODO add exception handling
       GroupSub groupSub;
       Optional<GroupSub> groupSubFromDB = groupSubRepository.findById(groupDiscussionInfo.getId());
       if(groupSubFromDB.isPresent()) {
           groupSub = groupSubFromDB.get();
           Optional<TelegramUser> first = groupSub.getUsers().stream()
                   .filter(it -> it.getChatId().equalsIgnoreCase(chatId))
                   .findFirst();
           if(first.isEmpty()) {
               groupSub.addUser(telegramUser);
           }
       } else {
           groupSub = new GroupSub();
           groupSub.addUser(telegramUser);
           groupSub.setId(groupDiscussionInfo.getId());
           groupSub.setTitle(groupDiscussionInfo.getTitle());
       }
       return groupSubRepository.save(groupSub);
   }
}
Para que o Spring Data funcione corretamente e um registro muitos para muitos seja criado, precisamos obter o usuário do nosso banco de dados para o grupo de assinaturas que estamos criando e adicioná-lo ao objeto GroupSub. Assim, ao transferirmos esta assinatura para salvar, também será criada uma conexão através da tabela group_x_user. Pode haver uma situação em que esse grupo de assinaturas já tenha sido criado e você só precise adicionar outro usuário a ele. Para isso, primeiro obtemos o ID do grupo do banco de dados, e se houver registro trabalhamos com ele, caso contrário criamos um novo. É importante ressaltar que para trabalhar com TelegramUser utilizamos TelegramUserService para seguir o último dos princípios SOLID. No momento, se não encontrarmos um registro por ID, simplesmente lanço uma exceção. Não está sendo processado de forma alguma agora: faremos isso bem no final, antes do MVP. Vamos escrever dois testes de unidade para a classe GroupSubServiceTest . De quais precisamos? Quero ter certeza de que o método save será chamado no GroupSubRepository e uma entidade com um único usuário será passada para o GroupSub - aquele que retornará o TelegramUserService para nós usando o ID fornecido. E a segunda opção, quando um grupo com o mesmo ID já está no banco de dados e este grupo já possui um usuário, e você precisa verificar se outro usuário será adicionado a este grupo e este objeto será salvo. Aqui está a implementação:
package com.github.javarushcommunity.jrtb.service;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.repository.GroupSubRepository;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Optional;

@DisplayName("Unit-level testing for GroupSubService")
public class GroupSubServiceTest {

   private GroupSubService groupSubService;
   private GroupSubRepository groupSubRepository;
   private TelegramUser newUser;

   private final static String CHAT_ID = "1";

   @BeforeEach
   public void init() {
       TelegramUserService telegramUserService = Mockito.mock(TelegramUserService.class);
       groupSubRepository = Mockito.mock(GroupSubRepository.class);
       groupSubService = new GroupSubServiceImpl(groupSubRepository, telegramUserService);

       newUser = new TelegramUser();
       newUser.setActive(true);
       newUser.setChatId(CHAT_ID);

       Mockito.when(telegramUserService.findByChatId(CHAT_ID)).thenReturn(Optional.of(newUser));
   }

   @Test
   public void shouldProperlySaveGroup() {
       //given

       GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo();
       groupDiscussionInfo.setId(1);
       groupDiscussionInfo.setTitle("g1");

       GroupSub expectedGroupSub = new GroupSub();
       expectedGroupSub.setId(groupDiscussionInfo.getId());
       expectedGroupSub.setTitle(groupDiscussionInfo.getTitle());
       expectedGroupSub.addUser(newUser);

       //when
       groupSubService.save(CHAT_ID, groupDiscussionInfo);

       //then
       Mockito.verify(groupSubRepository).save(expectedGroupSub);
   }

   @Test
   public void shouldProperlyAddUserToExistingGroup() {
       //given
       TelegramUser oldTelegramUser = new TelegramUser();
       oldTelegramUser.setChatId("2");
       oldTelegramUser.setActive(true);

       GroupDiscussionInfo groupDiscussionInfo = new GroupDiscussionInfo();
       groupDiscussionInfo.setId(1);
       groupDiscussionInfo.setTitle("g1");

       GroupSub groupFromDB = new GroupSub();
       groupFromDB.setId(groupDiscussionInfo.getId());
       groupFromDB.setTitle(groupDiscussionInfo.getTitle());
       groupFromDB.addUser(oldTelegramUser);

       Mockito.when(groupSubRepository.findById(groupDiscussionInfo.getId())).thenReturn(Optional.of(groupFromDB));

       GroupSub expectedGroupSub = new GroupSub();
       expectedGroupSub.setId(groupDiscussionInfo.getId());
       expectedGroupSub.setTitle(groupDiscussionInfo.getTitle());
       expectedGroupSub.addUser(oldTelegramUser);
       expectedGroupSub.addUser(newUser);

       //when
       groupSubService.save(CHAT_ID, groupDiscussionInfo);

       //then
       Mockito.verify(groupSubRepository).findById(groupDiscussionInfo.getId());
       Mockito.verify(groupSubRepository).save(expectedGroupSub);
   }

}
Também adicionei o método init() com a anotação BeforeEach. Dessa forma, você geralmente cria um método que será executado antes de cada teste ser executado e pode colocar nele uma lógica comum para todos os testes. No nosso caso, precisamos bloquear o TelegramUserService da mesma forma para todos os testes desta classe, por isso faz sentido transferir esta lógica para um método comum. Existem dois designs de mokito usados ​​​​aqui:
  • Mockito.when(o1.m1(a1)).thenReturn(o2) - nele dizemos que quando o método m1 é chamado no objeto o1 com o argumento a1 , o método retornará o objeto o2 . Esta é quase a funcionalidade mais importante do mockito - forçar o objeto simulado a retornar exatamente o que precisamos;

  • Mockito.verify(o1).m1(a1) - que verifica se o método m1 foi chamado no objeto o1 com o argumento a1 . Era possível, claro, usar o objeto retornado do método save, mas resolvi complicar um pouco mais mostrando outro método possível. Quando pode ser útil? Nos casos em que métodos de classes simuladas retornam nulos. Então sem Mockito.verify não haverá trabalho)))

Continuamos aderindo à ideia de que os testes precisam ser escritos, e muitos deles precisam ser escritos. A próxima etapa é trabalhar com a equipe do telegram bot.

Crie o comando /addGroupSub

Aqui precisamos realizar a seguinte lógica: se recebermos apenas um comando, sem nenhum contexto, ajudamos o usuário e entregamos a ele uma lista de todos os grupos com seus IDs para que ele passe as informações necessárias ao bot. E se o usuário enviar um comando ao bot com alguma outra(s) palavra(s), encontre um grupo com esse ID ou escreva que tal grupo não existe. Vamos adicionar um novo valor em nosso ename - CommandName:
ADD_GROUP_SUB("/addgroupsub")
Vamos passar do banco de dados para o bot de telegrama - crie a classe AddGroupSubCommand no pacote de comando:
package com.github.javarushcommunity.jrtb.command;

import com.github.javarushcommunity.jrtb.javarushclient.JavaRushGroupClient;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.service.GroupSubService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import org.telegram.telegrambots.meta.api.objects.Update;

import java.util.stream.Collectors;

import static com.github.javarushcommunity.jrtb.command.CommandName.ADD_GROUP_SUB;
import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId;
import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.SPACE;
import static org.apache.commons.lang3.StringUtils.isNumeric;

/**
* Add Group subscription {@link Command}.
*/
public class AddGroupSubCommand implements Command {

   private final SendBotMessageService sendBotMessageService;
   private final JavaRushGroupClient javaRushGroupClient;
   private final GroupSubService groupSubService;

   public AddGroupSubCommand(SendBotMessageService sendBotMessageService, JavaRushGroupClient javaRushGroupClient,
                             GroupSubService groupSubService) {
       this.sendBotMessageService = sendBotMessageService;
       this.javaRushGroupClient = javaRushGroupClient;
       this.groupSubService = groupSubService;
   }

   @Override
   public void execute(Update update) {
       if (getMessage(update).equalsIgnoreCase(ADD_GROUP_SUB.getCommandName())) {
           sendGroupIdList(getChatId(update));
           return;
       }
       String groupId = getMessage(update).split(SPACE)[1];
       String chatId = getChatId(update);
       if (isNumeric(groupId)) {
           GroupDiscussionInfo groupById = javaRushGroupClient.getGroupById(Integer.parseInt(groupId));
           if (isNull(groupById.getId())) {
               sendGroupNotFound(chatId, groupId);
           }
           GroupSub savedGroupSub = groupSubService.save(chatId, groupById);
           sendBotMessageService.sendMessage(chatId, "Подписал на группу " + savedGroupSub.getTitle());
       } else {
           sendGroupNotFound(chatId, groupId);
       }
   }

   private void sendGroupNotFound(String chatId, String groupId) {
       String groupNotFoundMessage = "Нет группы с ID = \"%s\"";
       sendBotMessageService.sendMessage(chatId, String.format(groupNotFoundMessage, groupId));
   }

   private void sendGroupIdList(String chatId) {
       String groupIds = javaRushGroupClient.getGroupList(GroupRequestArgs.builder().build()).stream()
               .map(group -> String.format("%s - %s \n", group.getTitle(), group.getId()))
               .collect(Collectors.joining());

       String message = "Whatбы подписаться на группу - передай комадну вместе с ID группы. \n" +
               "Например: /addGroupSub 16. \n\n" +
               "я подготовил список всех групп - выберай Howую хочешь :) \n\n" +
               "Name группы - ID группы \n\n" +
               "%s";

       sendBotMessageService.sendMessage(chatId, String.format(message, groupIds));
   }
}
Esta classe usa o método isNumeric da biblioteca apache-commons, então vamos adicioná-lo à nossa memória:
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>${apache.commons.version}</version>
</dependency>
E no bloco de propriedades:
<apache.commons.version>3.11</apache.commons.version>
Toda essa lógica está na classe. Leia atentamente. Se você tiver alguma dúvida/sugestão, escreva nos comentários. Depois disso, precisamos adicionar o comando ao CommandContainer em nosso mapa de comandos:
.put(ADD_GROUP_SUB.getCommandName(), new AddGroupSubCommand(sendBotMessageService, javaRushGroupClient, groupSubService))
E tudo para esta equipe. Eu gostaria de testar de alguma forma essa funcionalidade, mas até agora só consigo ver isso no banco de dados. Na terceira parte, adicionarei alterações do JRTB-6 para que possamos ver a lista de grupos nos quais um usuário está inscrito. Agora seria bom verificar tudo isso. Para isso, realizaremos todas as ações no Telegram e verificaremos no banco de dados. Como escrevemos testes, tudo deve ficar bem. O artigo já é bastante longo, então escreveremos um teste para AddGroupSubCommand mais tarde e adicionaremos TODO no código para não esquecer.

conclusões

Neste artigo, analisamos o trabalho de adição de funcionalidades em toda a aplicação, começando pelo banco de dados e terminando com o trabalho com o cliente que utiliza o bot. Normalmente, essas tarefas ajudam a entender o projeto e compreender sua essência. Entenda como funciona. Hoje em dia os temas não são fáceis, então não tenha vergonha: escreva suas dúvidas nos comentários, e tentarei respondê-las. Você gosta do projeto? Dê uma estrela no Github : assim ficará claro que eles estão interessados ​​no projeto, e ficarei feliz. Como se costuma dizer, um mestre fica sempre satisfeito quando seu trabalho é apreciado. O código conterá todas as três partes do STEP_6 e estará disponível antes deste artigo. Como descobrir isso? É fácil - entre no canal de telegramas , onde publico todas as informações sobre meus artigos sobre o bot de telegramas. Obrigado por ler! A parte 3 já está aqui .

Uma lista de todos os materiais da série está no início deste artigo.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION