Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/api/v1/user/me",
"/api/v1/auth/logout",
"/api/trips/**",
"/api/waypoints/**"
"/api/waypoints/**",
"/api/v1/news/views/export"
).authenticated()
.anyRequest().authenticated()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package fr.parisnanterre.greentrip.backend.controller;
import fr.parisnanterre.greentrip.backend.entity.FavoriteArticle;
import fr.parisnanterre.greentrip.backend.entity.User;
import fr.parisnanterre.greentrip.backend.service.FavoriteArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;



@RestController
@RequestMapping("/api/v1/favorites")
public class FavoriteArticleController {

@Autowired
private FavoriteArticleService service;

@GetMapping
public List<FavoriteArticle> getFavorites(@AuthenticationPrincipal User user) {
return service.getFavorites(user);
}

@PostMapping
public ResponseEntity<?> addFavorite(@AuthenticationPrincipal User user, @RequestBody FavoriteArticle article) {
service.addFavorite(user, article);
return ResponseEntity.ok().build();
}

@DeleteMapping
public ResponseEntity<?> removeFavorite(@AuthenticationPrincipal User user, @RequestParam String url) {
service.removeFavorite(user, url);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package fr.parisnanterre.greentrip.backend.controller;

import fr.parisnanterre.greentrip.backend.dto.ArticleViewRequest;
import fr.parisnanterre.greentrip.backend.entity.ArticleView;
import fr.parisnanterre.greentrip.backend.entity.User;
import fr.parisnanterre.greentrip.backend.repository.ArticleViewRepository;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import fr.parisnanterre.greentrip.backend.service.NewsService;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@RestController

@RequestMapping("/api/v1/news")
public class NewsTrackingController {

@Autowired
private ArticleViewRepository repository;

@Autowired
private NewsService newsService;

@GetMapping("/external-news")
public ResponseEntity<?> fetchExternalNews(@RequestParam String topic) {
try {
String response = newsService.fetchNewsByTopic(topic);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(500).body("Erreur lors de la récupération des actualités : " + e.getMessage());
}
}


@PostMapping("/view")
public ResponseEntity<?> trackArticleView(@AuthenticationPrincipal User user,
@RequestBody ArticleViewRequest request) {
ArticleView view = new ArticleView();
view.setTitle(request.getTitle());
view.setUrl(request.getUrl());
view.setViewedAt(LocalDateTime.now());
view.setUser(user);
repository.save(view);
return ResponseEntity.ok().build();
}

@GetMapping("/views")
public ResponseEntity<?> getUserViews(@AuthenticationPrincipal User user) {
return ResponseEntity.ok(repository.findByUserOrderByViewedAtDesc(user));
}

@GetMapping("/views/stats")
public ResponseEntity<?> getStats(@AuthenticationPrincipal User user) {
var views = repository.findByUser(user);
long total = views.size();
String lastTitle = views.isEmpty() ? null : views.get(views.size() - 1).getTitle();
return ResponseEntity.ok(new StatsResponse(total, lastTitle));
}
public record StatsResponse(long totalViews, String lastViewedTitle) {}

@GetMapping("/views/last-days")
public ResponseEntity<?> getLastNDaysViews(@AuthenticationPrincipal User user,
@RequestParam int days) {
LocalDateTime from = LocalDateTime.now().minusDays(days);
return ResponseEntity.ok(repository.findByUserAndViewedAtAfter(user, from));
}

@GetMapping("/views/top")
public ResponseEntity<?> getTopArticles() {
List<ArticleView> allViews = repository.findAll();
Map<String, Long> grouped = allViews.stream()
.collect(Collectors.groupingBy(ArticleView::getUrl, Collectors.counting()));

List<TopArticle> top = grouped.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.map(e -> new TopArticle(e.getKey(), e.getValue()))
.collect(Collectors.toList());

return ResponseEntity.ok(top);
}

public record TopArticle(String url, long views) {}

@GetMapping("/views/export")
public void exportCsv(@AuthenticationPrincipal User user, HttpServletResponse response) throws IOException {
List<ArticleView> views = repository.findByUserOrderByViewedAtDesc(user);
response.setContentType("text/csv");
response.setHeader("Content-Disposition", "attachment; filename=views.csv");

PrintWriter writer = response.getWriter();
writer.println("Title,URL,ViewedAt");
for (ArticleView v : views) {
writer.printf("\"%s\",%s,%s\n",
v.getTitle().replaceAll("\"", "\"\""),
v.getUrl(),
v.getViewedAt());
}
writer.flush();
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package fr.parisnanterre.greentrip.backend.controller;

import fr.parisnanterre.greentrip.backend.entity.FavoriteArticle;
import fr.parisnanterre.greentrip.backend.entity.User;
import fr.parisnanterre.greentrip.backend.service.FavoriteArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import fr.parisnanterre.greentrip.backend.repository.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import fr.parisnanterre.greentrip.backend.entity.User;
import java.util.List;

@RestController
@RequestMapping("/api/v1/user")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fr.parisnanterre.greentrip.backend.dto;

public class ArticleViewRequest {
private String title;
private String url;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fr.parisnanterre.greentrip.backend.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Data
@NoArgsConstructor
public class ArticleView {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;

private String url;

private LocalDateTime viewedAt;

@ManyToOne
private User user;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fr.parisnanterre.greentrip.backend.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import fr.parisnanterre.greentrip.backend.entity.User;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;


@Entity
@Table(name = "favorite_article")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FavoriteArticle {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;
private String url;
private String description;
private String imageUrl;
private String publishedAt;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.parisnanterre.greentrip.backend.repository;

import fr.parisnanterre.greentrip.backend.entity.ArticleView;
import org.springframework.data.jpa.repository.JpaRepository;
import fr.parisnanterre.greentrip.backend.entity.User;
import java.time.LocalDateTime;
import java.util.List;

public interface ArticleViewRepository extends JpaRepository<ArticleView, Long> {
List<ArticleView> findByUser(User user);
List<ArticleView> findByUserOrderByViewedAtDesc(User user);
List<ArticleView> findByUserAndViewedAtAfter(User user, LocalDateTime after);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.parisnanterre.greentrip.backend.repository;
import fr.parisnanterre.greentrip.backend.entity.FavoriteArticle;
import fr.parisnanterre.greentrip.backend.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface FavoriteArticleRepository extends JpaRepository<FavoriteArticle, Long> {
List<FavoriteArticle> findByUser(User user);
boolean existsByUserAndUrl(User user, String url);
void deleteByUserAndUrl(User user, String url);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fr.parisnanterre.greentrip.backend.service;
import fr.parisnanterre.greentrip.backend.entity.FavoriteArticle;
import fr.parisnanterre.greentrip.backend.entity.User;
import fr.parisnanterre.greentrip.backend.repository.FavoriteArticleRepository;
import java.util.List;
import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class FavoriteArticleService {

@Autowired
private FavoriteArticleRepository repository;

public List<FavoriteArticle> getFavorites(User user) {
return repository.findByUser(user);
}

public void addFavorite(User user, FavoriteArticle article) {
if (!repository.existsByUserAndUrl(user, article.getUrl())) {
article.setUser(user);
repository.save(article);
}
}

@Transactional
public void removeFavorite(User user, String url) {
repository.deleteByUserAndUrl(user, url);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fr.parisnanterre.greentrip.backend.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class NewsService {

@Autowired
private RestTemplate restTemplate;

private static final String API_KEY = "pub_3388b5bcd2cc4e9ea999762df279e09c";
private static final String BASE_URL = "https://newsdata.io/api/1/news";

public String fetchNewsByTopic(String topic) {
String url = BASE_URL + "?apikey=" + API_KEY + "&language=fr&q=" + topic;
return restTemplate.getForObject(url, String.class);
}
}
2 changes: 1 addition & 1 deletion app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ spring:
add-mappings: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql-docker:3306/greentrip
url: jdbc:mysql://mysql-docker:3307/greentrip
username: root
password: root
hikari:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: greentrip
ports:
- "3306:3306"
- "3307:3306"
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot" ]
interval: 10s
Expand Down
Loading