Facilite a programação para o Reddit

Facilite a programação para o Reddit

1. Visão geral

Neste artigo, vamoscontinue the case study eadd a new feature to the Reddit application, com o objetivo de tornar muito mais simples agendar artigos.

Em vez de adicionar lentamente todos os artigos manualmente à interface do usuário da programação, o usuário agora pode ter apenas alguns sites favoritos para postar artigos no Reddit. Vamos usar RSS para fazer isso.

2. A EntidadeSite

Primeiro - vamos criar uma entidade para representar o site:

@Entity
public class Site {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String url;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

Observe que o campourl representa a URL dethe RSS feed of the site.

3. O Repositório e o Serviço

Avançar - vamos criar o repositório para trabalhar com a nova entidade do site:

public interface SiteRepository extends JpaRepository {
    List findByUser(User user);
}

E o serviço:

public interface ISiteService {

    List getSitesByUser(User user);

    void saveSite(Site site);

    Site findSiteById(Long siteId);

    void deleteSiteById(Long siteId);
}
@Service
public class SiteService implements ISiteService {

    @Autowired
    private SiteRepository repo;

    @Override
    public List getSitesByUser(User user) {
        return repo.findByUser(user);
    }

    @Override
    public void saveSite(Site site) {
        repo.save(site);
    }

    @Override
    public Site findSiteById(Long siteId) {
        return repo.findOne(siteId);
    }

    @Override
    public void deleteSiteById(Long siteId) {
        repo.delete(siteId);
    }
}

4. Carregar dados do feed

Agora - vamos ver como carregar os detalhes dos artigos do feed do site usando a Biblioteca de Roma.

Primeiro, precisaremos adicionar Roma em nossopom.xml:


    com.rometools
    rome
    1.5.0

E, em seguida, use-o para analisar os feeds dos sites:

public List getArticlesFromSite(Long siteId) {
    Site site = repo.findOne(siteId);
    return getArticlesFromSite(site);
}

List getArticlesFromSite(Site site) {
    List entries;
    try {
        entries = getFeedEntries(site.getUrl());
    } catch (Exception e) {
        throw new FeedServerException("Error Occurred while parsing feed", e);
    }
    return parseFeed(entries);
}

private List getFeedEntries(String feedUrl)
  throws IllegalArgumentException, FeedException, IOException {
    URL url = new URL(feedUrl);
    SyndFeed feed = new SyndFeedInput().build(new XmlReader(url));
    return feed.getEntries();
}

private List parseFeed(List entries) {
    List articles = new ArrayList();
    for (SyndEntry entry : entries) {
        articles.add(new SiteArticle(
          entry.getTitle(), entry.getLink(), entry.getPublishedDate()));
    }
    return articles;
}

Finalmente - aqui está o DTO simples que usaremos na resposta:

public class SiteArticle {
    private String title;
    private String link;
    private Date publishDate;
}

5. Manipulação de exceção

Observe como, ao analisar o feed, estamos agrupando toda a lógica de análise em um blocotry-catche - no caso de uma exceção (qualquer exceção) - estamos agrupando e lançando.

A razão para isso é simples -we need to control the type of exception that gets thrown out of the parsing process - para que possamos lidar com essa exceção e fornecer uma resposta adequada ao cliente da API:

@ExceptionHandler({ FeedServerException.class })
public ResponseEntity handleFeed(RuntimeException ex, WebRequest request) {
    logger.error("500 Status Code", ex);
    String bodyOfResponse = ex.getLocalizedMessage();
    return new ResponseEntity(bodyOfResponse, new HttpHeaders(),
      HttpStatus.INTERNAL_SERVER_ERROR);
}




6. A página de sites

6.1. Exibir os sites

Primeiro, veremos como mostrar a lista de sites pertencentes ao usuário conectado:

@RequestMapping(value = "/sites")
@ResponseBody
public List getSitesList() {
    return service.getSitesByUser(getCurrentUser());
}

E aqui está o front end muito simples:

Site NameFeed URLActions

6.2. Adicionar um novo site

A seguir, vamos ver como um usuário pode criar um novo site favorito:

@RequestMapping(value = "/sites", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void addSite(Site site) {
    if (!service.isValidFeedUrl(site.getUrl())) {
        throw new FeedServerException("Invalid Feed Url");
    }
    site.setUser(getCurrentUser());
    service.saveSite(site);
}

E aqui está o - novamente muito simples - lado do cliente:

6.3. Validando um Feed

A validação de um novo feed é uma operação um pouco cara - precisamos recuperar o feed e analisá-lo para validá-lo completamente. Aqui está o método de serviço simples:

public boolean isValidFeedUrl(String feedUrl) {
    try {
        return getFeedEntries(feedUrl).size() > 0;
    } catch (Exception e) {
        return false;
    }
}

6.3. Excluir um Site

Agora, vamos ver como o usuário podedelete a site from their list of favorite sites:

@RequestMapping(value = "/sites/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deleteSite(@PathVariable("id") Long id) {
    service.deleteSiteById(id);
}

E aqui o método de nível de serviço - novamente muito simples -:

public void deleteSiteById(Long siteId) {
    repo.delete(siteId);
}

7. Agendar uma postagem de um site

Agora - vamos começar a usar esses sites e implementar uma maneira básica de um usuário agendar uma nova postagem para ir para o Reddit não manualmente, mas carregando um artigo de um site existente.

7.1. Modificar formulário de agendamento

Vamos começar com o site do cliente e modificar oschedulePostForm.html existente - vamos adicionar:


      Observe que adicionamos:

      • o botão - “Load from my Sites” - para iniciar o processo

      • o pop-up - mostrando a lista de sites e seus artigos

      7.2. Carregue os sites

      Carregar os sites no pop-up é relativamente fácil com um pouco de javascript:

      $('#myModal').on('shown.bs.modal', function () {
          if($("#siteList").children().length > 0)
              return;
          $.get("sites", function(data){
              $.each(data, function( index, site ) {
                  $("#siteList").append('
    • '+site.name+'
    • ') }); }); });

      8. Testes de integração

      Finalmente - vamos testar nossoSiteService em dois formatos de feed diferentes:

      public class SiteIntegrationTest {
      
          private ISiteService service;
      
          @Before
          public void init() {
              service = new SiteService();
          }
      
          @Test
          public void whenUsingServiceToReadWordpressFeed_thenCorrect() {
              Site site = new Site("/feed/");
              List articles = service.getArticlesFromSite(site);
      
              assertNotNull(articles);
              for (SiteArticle article : articles) {
                  assertNotNull(article.getTitle());
                  assertNotNull(article.getLink());
              }
          }
      
          @Test
          public void whenUsingRomeToReadBloggerFeed_thenCorrect() {
              Site site = new Site("http://blogname.blogspot.com/feeds/posts/default");
              List articles = service.getArticlesFromSite(site);
      
              assertNotNull(articles);
              for (SiteArticle article : articles) {
                  assertNotNull(article.getTitle());
                  assertNotNull(article.getLink());
              }
          }
      }

      Há claramente um pouco de duplicação aqui, mas podemos cuidar disso mais tarde.

      9. Conclusão

      Nesta edição, focamos em um novo recurso pequeno - simplificando o agendamento da publicação no Reddit.