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
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 Name Feed URL Actions
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+' ')
});
});
});
7.3. Carregue as postagens de um site
Quando o usuário seleciona um site da lista, precisamos mostrar os artigos desse site - novamente com alguns js básicos:
function loadArticles(siteID,siteName){
$("#dropdownMenu1").html(siteName);
$.get("sites/articles?id="+siteID, function(data){
$("#articleList").html('');
$("#dropdownMenu2").html('Choose Article');
$.each(data, function( index, article ) {
$("#articleList").append(
''+article.title+' '+
new Date(article.publishDate).toUTCString()+' ')
});
}).fail(function(error){
alert(error.responseText);
});
}
Obviamente, isso se conecta a uma operação simples do lado do servidor para carregar os artigos de um site:
@RequestMapping(value = "/sites/articles")
@ResponseBody
public List getSiteArticles(@RequestParam("id") Long siteId) {
return service.getArticlesFromSite(siteId);
}
Por fim, obtemos os dados do artigo, preenchemos o formulário e agendamos o artigo para o Reddit:
var title = "";
var link = "";
function chooseArticle(selectedTitle,selectedLink){
$("#dropdownMenu2").html(selectedTitle);
title=selectedTitle;
link = selectedLink;
}
function load(){
$("input[name='title']").val(title);
$("input[name='url']").val(link);
}
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.