refactor pour intégrer l'architecture 3-tiers

This commit is contained in:
2025-12-02 23:21:44 +01:00
parent f0e20be396
commit 6a3569029d
41 changed files with 530 additions and 226 deletions

View File

@@ -1,49 +1,46 @@
package local.epul4a.fotosharing.controller; package local.epul4a.fotosharing.controller;
import local.epul4a.fotosharing.model.Partage; import local.epul4a.fotosharing.dto.CommentaireDTO;
import local.epul4a.fotosharing.model.Photo; import local.epul4a.fotosharing.dto.PartageDTO;
import local.epul4a.fotosharing.model.Utilisateur; import local.epul4a.fotosharing.dto.PhotoDTO;
import local.epul4a.fotosharing.repository.PartageRepository;
import local.epul4a.fotosharing.repository.PhotoRepository;
import local.epul4a.fotosharing.repository.UtilisateurRepository;
import local.epul4a.fotosharing.security.CustomUserDetails;
import local.epul4a.fotosharing.service.CommentaireService; import local.epul4a.fotosharing.service.CommentaireService;
import local.epul4a.fotosharing.service.PartageService;
import local.epul4a.fotosharing.service.PhotoService; import local.epul4a.fotosharing.service.PhotoService;
import local.epul4a.fotosharing.service.UtilisateurService;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.PathResource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@Controller @Controller
public class PhotoController { public class PhotoController {
private final PhotoService photoService; private final PhotoService photoService;
private final PhotoRepository photoRepository;
private final CommentaireService commentaireService; private final CommentaireService commentaireService;
private final UtilisateurRepository utilisateurRepository; private final PartageService partageService;
private final PartageRepository partageRepository; private final UtilisateurService utilisateurService;
public PhotoController(PhotoService photoService, PhotoRepository photoRepository, CommentaireService commentaireService, UtilisateurRepository utilisateurRepository, PartageRepository partageRepository) { public PhotoController(
PhotoService photoService,
CommentaireService commentaireService,
PartageService partageService,
UtilisateurService utilisateurService
) {
this.photoService = photoService; this.photoService = photoService;
this.photoRepository = photoRepository;
this.commentaireService = commentaireService; this.commentaireService = commentaireService;
this.utilisateurRepository = utilisateurRepository; this.partageService = partageService;
this.partageRepository = partageRepository; this.utilisateurService = utilisateurService;
} }
/* ========================== UPLOAD ========================== */
@GetMapping("/upload") @GetMapping("/upload")
public String uploadForm() { public String uploadForm() {
return "upload"; return "upload";
@@ -52,147 +49,142 @@ public class PhotoController {
@PostMapping("/upload") @PostMapping("/upload")
public String doUpload(@RequestParam("file") MultipartFile file, public String doUpload(@RequestParam("file") MultipartFile file,
@RequestParam(value = "visibilite", defaultValue = "PRIVATE") String visibilite, @RequestParam(value = "visibilite", defaultValue = "PRIVATE") String visibilite,
Authentication authentication, Authentication auth,
Model model) { Model model) {
try { try {
String email = authentication.getName(); // l'email de l'utilisateur connecté photoService.store(file, visibilite, auth.getName());
Photo p = photoService.store(file, visibilite, email); return "redirect:/mes-photos";
model.addAttribute("message", "Upload OK : " + p.getId()); } catch (Exception ex) {
return "redirect:/"; model.addAttribute("error", ex.getMessage());
} catch (Exception e) {
model.addAttribute("error", e.getMessage());
return "upload"; return "upload";
} }
} }
/* ========================== RAW IMAGE ========================== */
@GetMapping("/photo/{id}/raw") @GetMapping("/photo/{id}/raw")
public ResponseEntity<Resource> rawPhoto(@PathVariable("id") Long id) { public ResponseEntity<Resource> rawPhoto(@PathVariable Long id) {
Photo photo = photoRepository.findById(id).orElse(null);
if (photo == null) { PhotoDTO photo = photoService.getPhotoById(id);
if (photo == null)
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
}
Path p = photoService.loadAsPath(photo.getUuidFichier()); Resource r = photoService.loadAsResource(photo.getUuidFichier());
Resource r = new PathResource(p); if (!r.exists())
if (!r.exists()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
}
String contentType = "application/octet-stream";
try {
contentType = Files.probeContentType(p);
} catch (Exception ignored) {
}
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType)) .contentType(MediaType.IMAGE_JPEG) // simplifiable, ou détecté dans service
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + photo.getNomFichierOriginal() + "\"")
.body(r); .body(r);
} }
/* ========================== MES PHOTOS ========================== */
@GetMapping("/mes-photos") @GetMapping("/mes-photos")
public String mesPhotos( public String mesPhotos(
@RequestParam(name = "pagePrivees", defaultValue = "0") int pagePrivees, @RequestParam(defaultValue = "0") int pagePrivees,
@RequestParam(name = "pagePubliques", defaultValue = "0") int pagePubliques, @RequestParam(defaultValue = "0") int pagePubliques,
@RequestParam(name = "pagePartagees", defaultValue = "0") int pagePartagees, @RequestParam(defaultValue = "0") int pagePartagees,
@RequestParam(name = "pageMesPartagees", defaultValue = "0") int pageMesPartagees, @RequestParam(defaultValue = "0") int pageMesPartagees,
Model model, Authentication auth, Model model) {
Authentication auth
) { String email = auth.getName();
String email = auth.getName();
// Chaque liste utilise sa propre pagination model.addAttribute("photosPrivees",
model.addAttribute("photosPrivees", photoService.listPrivatePhotos(email, pagePrivees, 12)); photoService.listPrivatePhotos(email, pagePrivees, 12));
model.addAttribute("photosPubliques", photoService.listPublicPhotos(email, pagePubliques, 12));
model.addAttribute("photosPartagees", photoService.listSharedWith(email, pagePartagees, 12)); model.addAttribute("photosPubliques",
model.addAttribute("mesPhotosPartagees", photoService.listSharedPhotos(email, pageMesPartagees, 12)); photoService.listPublicPhotos(email, pagePubliques, 12));
model.addAttribute("photosPartagees",
photoService.listSharedWith(email, pagePartagees, 12));
model.addAttribute("mesPhotosPartagees",
photoService.listSharedPhotos(email, pageMesPartagees, 12));
// Ajouter les 3 index séparés
model.addAttribute("pagePrivees", pagePrivees); model.addAttribute("pagePrivees", pagePrivees);
model.addAttribute("pagePubliques", pagePubliques); model.addAttribute("pagePubliques", pagePubliques);
model.addAttribute("pagePartagees", pagePartagees); model.addAttribute("pagePartagees", pagePartagees);
model.addAttribute("pageMesPartagees", pageMesPartagees); model.addAttribute("pageMesPartagees", pageMesPartagees);
return "mes-photos"; return "mes-photos";
} }
/* ========================== GALERIE ========================== */
@GetMapping("/galerie") @GetMapping("/galerie")
public String galerie(@RequestParam(defaultValue = "0") int page, Model model) { public String galerie(@RequestParam(defaultValue = "0") int page, Model model) {
Page<Photo> photosPage = photoService.listPublic(page, 12);
Page<PhotoDTO> photosPage = photoService.listPublic(page, 12);
model.addAttribute("photosPage", photosPage); model.addAttribute("photosPage", photosPage);
model.addAttribute("currentPage", page); model.addAttribute("currentPage", page);
return "galerie"; return "galerie";
} }
/* ========================== DETAIL PHOTO ========================== */
@GetMapping("/photo/{id}") @GetMapping("/photo/{id}")
@PreAuthorize("@securityService.canAccessPhoto(authentication, #id)") @PreAuthorize("@securityService.canAccessPhoto(authentication, #id)")
public String viewPhoto(@PathVariable Long id, public String viewPhoto(@PathVariable Long id,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
Model model, Authentication auth,
Authentication auth) { Model model) {
Photo photo = photoRepository.findById(id).orElse(null);
if (photo == null) { PhotoDTO photo = photoService.getPhotoById(id);
if (photo == null)
return "redirect:/galerie"; return "redirect:/galerie";
}
model.addAttribute("photo", photo); model.addAttribute("photo", photo);
// Pagination des commentaires
model.addAttribute("commentairesPage", Page<CommentaireDTO> commentaires =
commentaireService.listByPhoto(id, page, 10)); commentaireService.listByPhoto(id, page, 10);
model.addAttribute("commentairesPage", commentaires);
model.addAttribute("currentPage", page); model.addAttribute("currentPage", page);
// utilisateur connecté (peut être null)
String currentUser = (auth != null ? auth.getName() : null); String currentUser = (auth != null ? auth.getName() : null);
model.addAttribute("currentUser", currentUser); model.addAttribute("currentUser", currentUser);
// Liste des partages
List<Partage> partages = partageRepository.findByPhoto_Id(id); model.addAttribute("partages",
model.addAttribute("partages", partages); partageService.getPartagesForPhoto(id));
return "photo-detail"; return "photo-detail";
} }
/* ========================== COMMENTAIRES ========================== */
@PostMapping("/photo/{id}/comment") @PostMapping("/photo/{id}/comment")
public String addComment(@PathVariable Long id, public String addComment(@PathVariable Long id,
@RequestParam String contenu, @RequestParam String contenu,
Authentication auth) { Authentication auth) {
if (auth == null) {
return "redirect:/login"; commentaireService.addComment(id, auth.getName(), contenu);
}
String email = auth.getName();
commentaireService.addComment(id, email, contenu);
return "redirect:/photo/" + id; return "redirect:/photo/" + id;
} }
/* ========================== PARTAGE PHOTO ========================== */
@PostMapping("/photo/{id}/share") @PostMapping("/photo/{id}/share")
@PreAuthorize("@securityService.canAccessPhoto(authentication, #id)") @PreAuthorize("@securityService.canAccessPhoto(authentication, #id)")
public String share(@PathVariable Long id, public String sharePhoto(@PathVariable Long id,
@RequestParam String email, @RequestParam String email,
Authentication auth) { @RequestParam String permission,
// Vérifier que c'est le propriétaire Authentication auth) {
Photo photo = photoRepository.findById(id).orElse(null);
if (photo == null) return "redirect:/galerie"; partageService.share(id, email, permission, auth.getName());
if (!photo.getProprietaire().getEmail().equals(auth.getName())) {
return "redirect:/photo/" + id + "?error=notowner";
}
// Trouver utilisateur
Utilisateur user = utilisateurRepository.findByEmail(email).orElse(null);
if (user == null) {
return "redirect:/photo/" + id + "?error=usernotfound";
}
// Ajouter partage
Partage p = new Partage();
p.setPhoto(photo);
p.setUtilisateur(user);
partageRepository.save(p);
return "redirect:/photo/" + id + "?shared=ok"; return "redirect:/photo/" + id + "?shared=ok";
} }
@GetMapping("/photo/{id}/unshare/{email}") @GetMapping("/photo/{id}/unshare/{email}")
public String unshare(@PathVariable Long id, @PathVariable String email) { public String unshare(@PathVariable Long id, @PathVariable String email) {
photoService.unshare(id, email);
partageService.unshare(id, email);
return "redirect:/photo/" + id; return "redirect:/photo/" + id;
} }
}
}

View File

@@ -0,0 +1,15 @@
package local.epul4a.fotosharing.dto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class CommentaireDTO {
private Long id;
private String contenu;
private LocalDateTime dateCommentaire;
private UtilisateurDTO auteur;
}

View File

@@ -0,0 +1,13 @@
package local.epul4a.fotosharing.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PartageDTO {
private Long id;
private UtilisateurDTO utilisateur;
private String permission; // READ / COMMENT / ADMIN
private PhotoDTO photo;
}

View File

@@ -0,0 +1,19 @@
package local.epul4a.fotosharing.dto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class PhotoDTO {
private Long id;
private String nomFichierOriginal;
private String uuidFichier;
private LocalDateTime dateUpload;
private String visibilite;
private UtilisateurDTO proprietaire;
}

View File

@@ -0,0 +1,14 @@
package local.epul4a.fotosharing.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UtilisateurDTO {
private Long id;
private String email;
private String nom;
private String prenom;
}

View File

@@ -0,0 +1,18 @@
package local.epul4a.fotosharing.mapper;
import local.epul4a.fotosharing.dto.CommentaireDTO;
import local.epul4a.fotosharing.model.Commentaire;
public class CommentaireMapper {
public static CommentaireDTO toDTO(Commentaire c) {
if (c == null) return null;
CommentaireDTO dto = new CommentaireDTO();
dto.setId(c.getId());
dto.setContenu(c.getContenu());
dto.setDateCommentaire(c.getDateCommentaire());
// Auteur via mapper dédié
dto.setAuteur(UtilisateurMapper.toDTO(c.getAuteur()));
return dto;
}
}

View File

@@ -0,0 +1,20 @@
package local.epul4a.fotosharing.mapper;
import local.epul4a.fotosharing.dto.PartageDTO;
import local.epul4a.fotosharing.model.Partage;
public class PartageMapper {
public static PartageDTO toDTO(Partage p) {
if (p == null) return null;
PartageDTO dto = new PartageDTO();
dto.setId(p.getId());
dto.setUtilisateur(UtilisateurMapper.toDTO(p.getUtilisateur()));
dto.setPhoto(PhotoMapper.toDTO(p.getPhoto()));
dto.setPermission(p.getPermission().name());
return dto;
}
}

View File

@@ -0,0 +1,28 @@
package local.epul4a.fotosharing.mapper;
import local.epul4a.fotosharing.dto.PhotoDTO;
import local.epul4a.fotosharing.dto.UtilisateurDTO;
import local.epul4a.fotosharing.model.Photo;
import local.epul4a.fotosharing.model.Utilisateur;
public class PhotoMapper {
public static PhotoDTO toDTO(Photo p) {
if (p == null) return null;
PhotoDTO dto = new PhotoDTO();
dto.setId(p.getId());
dto.setNomFichierOriginal(p.getNomFichierOriginal());
dto.setUuidFichier(p.getUuidFichier());
dto.setDateUpload(p.getDateUpload());
dto.setVisibilite(p.getVisibilite().name());
Utilisateur u = p.getProprietaire();
if (u != null) {
UtilisateurDTO uDTO = new UtilisateurDTO();
uDTO.setId(u.getId());
uDTO.setEmail(u.getEmail());
uDTO.setNom(u.getNom());
uDTO.setPrenom(u.getPrenom());
dto.setProprietaire(uDTO);
}
return dto;
}
}

View File

@@ -0,0 +1,17 @@
package local.epul4a.fotosharing.mapper;
import local.epul4a.fotosharing.dto.UtilisateurDTO;
import local.epul4a.fotosharing.model.Utilisateur;
public class UtilisateurMapper {
public static UtilisateurDTO toDTO(Utilisateur u) {
if (u == null) return null;
UtilisateurDTO dto = new UtilisateurDTO();
dto.setId(u.getId());
dto.setEmail(u.getEmail());
dto.setNom(u.getNom());
dto.setPrenom(u.getPrenom());
return dto;
}
}

View File

@@ -8,6 +8,11 @@ import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class Partage { public class Partage {
public enum Permission {
READ,
COMMENT,
ADMIN
}
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -21,5 +26,7 @@ public class Partage {
@JoinColumn(name = "id_utilisateur") @JoinColumn(name = "id_utilisateur")
private Utilisateur utilisateur; private Utilisateur utilisateur;
// Getters & Setters @Enumerated(EnumType.STRING)
private Permission permission = Permission.READ;
} }

View File

@@ -1,5 +1,6 @@
package local.epul4a.fotosharing.service; package local.epul4a.fotosharing.service;
import local.epul4a.fotosharing.dto.CommentaireDTO;
import local.epul4a.fotosharing.model.Commentaire; import local.epul4a.fotosharing.model.Commentaire;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -8,6 +9,6 @@ import java.util.List;
public interface CommentaireService { public interface CommentaireService {
List<Commentaire> listByPhoto(Long photoId); List<Commentaire> listByPhoto(Long photoId);
void addComment(Long photoId, String email, String contenu); void addComment(Long photoId, String email, String contenu);
Page<Commentaire> listByPhoto(Long photoId, int page, int size); Page<CommentaireDTO> listByPhoto(Long photoId, int page, int size);
} }

View File

@@ -0,0 +1,10 @@
package local.epul4a.fotosharing.service;
import local.epul4a.fotosharing.dto.PartageDTO;
import java.util.List;
public interface PartageService {
void share(Long photoId, String targetEmail, String permission, String ownerEmail);
void unshare(Long photoId, String targetEmail);
List<PartageDTO> getPartagesForPhoto(Long photoId);
}

View File

@@ -1,6 +1,8 @@
package local.epul4a.fotosharing.service; package local.epul4a.fotosharing.service;
import local.epul4a.fotosharing.dto.PhotoDTO;
import local.epul4a.fotosharing.model.Photo; import local.epul4a.fotosharing.model.Photo;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
@@ -8,19 +10,14 @@ import java.nio.file.Path;
import java.util.List; import java.util.List;
public interface PhotoService { public interface PhotoService {
Photo store(MultipartFile file, String visibilite, String ownerEmail) throws IOException; PhotoDTO store(MultipartFile file, String visibilite, String ownerEmail);
Path loadAsPath(String uuidFile); PhotoDTO getPhotoById(Long id);
List<Photo> listByOwner(String email); Resource loadAsResource(String uuidFichier);
List<Photo> listPublicPhotos(); Page<PhotoDTO> listPublic(int page, int size);
List<Photo> listSharedWith(String email); Page<PhotoDTO> listPrivatePhotos(String email, int page, int size);
List<Photo> listPrivatePhotos(String email); Page<PhotoDTO> listPublicPhotos(String email, int page, int size);
List<Photo> listPublicPhotos(String email); Page<PhotoDTO> listSharedWith(String email, int page, int size);
void unshare(Long photoId, String email); Page<PhotoDTO> listSharedPhotos(String email, int page, int size);
Page<Photo> listPublic(int page, int size);
Page<Photo> listPrivatePhotos(String email, int page, int size);
Page<Photo> listPublicPhotos(String email, int page, int size);
Page<Photo> listSharedWith(String email, int page, int size);
Page<Photo> listSharedPhotos(String email, int page, int size);

View File

@@ -0,0 +1,8 @@
package local.epul4a.fotosharing.service;
import local.epul4a.fotosharing.dto.UtilisateurDTO;
public interface UtilisateurService {
UtilisateurDTO findByEmail(String email);
boolean existsByEmail(String email);
}

View File

@@ -1,5 +1,7 @@
package local.epul4a.fotosharing.service.impl; package local.epul4a.fotosharing.service.impl;
import local.epul4a.fotosharing.dto.CommentaireDTO;
import local.epul4a.fotosharing.mapper.CommentaireMapper;
import local.epul4a.fotosharing.model.Commentaire; import local.epul4a.fotosharing.model.Commentaire;
import local.epul4a.fotosharing.model.Photo; import local.epul4a.fotosharing.model.Photo;
import local.epul4a.fotosharing.model.Utilisateur; import local.epul4a.fotosharing.model.Utilisateur;
@@ -51,9 +53,14 @@ public class CommentaireServiceImpl implements CommentaireService {
commentaireRepository.save(c); commentaireRepository.save(c);
} }
@Override @Override
public Page<Commentaire> listByPhoto(Long photoId, int page, int size) { public Page<CommentaireDTO> listByPhoto(Long photoId, int page, int size) {
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
return commentaireRepository.findByPhoto_Id(photoId, pageable);
Page<Commentaire> commentaires =
commentaireRepository.findByPhoto_Id(photoId, pageable);
return commentaires.map(CommentaireMapper::toDTO);
} }
} }

View File

@@ -0,0 +1,78 @@
package local.epul4a.fotosharing.service.impl;
import local.epul4a.fotosharing.dto.PartageDTO;
import local.epul4a.fotosharing.mapper.PartageMapper;
import local.epul4a.fotosharing.model.Partage;
import local.epul4a.fotosharing.model.Photo;
import local.epul4a.fotosharing.model.Utilisateur;
import local.epul4a.fotosharing.repository.PartageRepository;
import local.epul4a.fotosharing.repository.PhotoRepository;
import local.epul4a.fotosharing.repository.UtilisateurRepository;
import local.epul4a.fotosharing.service.PartageService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PartageServiceImpl implements PartageService {
private final PartageRepository partageRepository;
private final PhotoRepository photoRepository;
private final UtilisateurRepository utilisateurRepository;
public PartageServiceImpl(
PartageRepository partageRepository,
PhotoRepository photoRepository,
UtilisateurRepository utilisateurRepository
) {
this.partageRepository = partageRepository;
this.photoRepository = photoRepository;
this.utilisateurRepository = utilisateurRepository;
}
@Override
public List<PartageDTO> getPartagesForPhoto(Long photoId) {
return partageRepository.findByPhoto_Id(photoId)
.stream()
.map(PartageMapper::toDTO)
.toList();
}
@Override
public void share(Long photoId, String targetEmail, String permissionStr, String ownerEmail) {
Photo photo = photoRepository.findById(photoId)
.orElseThrow(() -> new RuntimeException("Photo introuvable"));
if (!photo.getProprietaire().getEmail().equals(ownerEmail))
throw new RuntimeException("Vous n'êtes pas propriétaire");
Utilisateur target = utilisateurRepository.findByEmail(targetEmail)
.orElseThrow(() -> new RuntimeException("Utilisateur introuvable"));
if (partageRepository.existsByPhoto_IdAndUtilisateur_Email(photoId, targetEmail))
return;
Partage.Permission permission = Partage.Permission.valueOf(permissionStr);
Partage partage = new Partage();
partage.setPhoto(photo);
partage.setUtilisateur(target);
partage.setPermission(permission);
partageRepository.save(partage);
}
@Override
public void unshare(Long photoId, String targetEmail) {
Partage partage = partageRepository.findByPhoto_Id(photoId)
.stream()
.filter(p -> p.getUtilisateur().getEmail().equals(targetEmail))
.findFirst()
.orElse(null);
if (partage != null)
partageRepository.delete(partage);
}
}

View File

@@ -1,5 +1,7 @@
package local.epul4a.fotosharing.service.impl; package local.epul4a.fotosharing.service.impl;
import local.epul4a.fotosharing.dto.PhotoDTO;
import local.epul4a.fotosharing.mapper.PhotoMapper;
import local.epul4a.fotosharing.model.Partage; import local.epul4a.fotosharing.model.Partage;
import local.epul4a.fotosharing.model.Photo; import local.epul4a.fotosharing.model.Photo;
import local.epul4a.fotosharing.model.Utilisateur; import local.epul4a.fotosharing.model.Utilisateur;
@@ -8,15 +10,15 @@ import local.epul4a.fotosharing.repository.PhotoRepository;
import local.epul4a.fotosharing.repository.UtilisateurRepository; import local.epul4a.fotosharing.repository.UtilisateurRepository;
import local.epul4a.fotosharing.service.PhotoService; import local.epul4a.fotosharing.service.PhotoService;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page; import org.springframework.core.io.Resource;
import org.springframework.data.domain.PageImpl; import org.springframework.core.io.UrlResource;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.*;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.*; import java.nio.file.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -27,134 +29,130 @@ public class PhotoServiceImpl implements PhotoService {
@Value("${file.upload-dir}") @Value("${file.upload-dir}")
private String uploadDir; private String uploadDir;
private final PhotoRepository photoRepository; private final PhotoRepository photoRepository;
private final UtilisateurRepository utilisateurRepository; private final UtilisateurRepository utilisateurRepository;
private final PartageRepository partageRepository; private final PartageRepository partageRepository;
public PhotoServiceImpl(PhotoRepository photoRepository, UtilisateurRepository utilisateurRepository, PartageRepository partageRepository) { public PhotoServiceImpl(
PhotoRepository photoRepository,
UtilisateurRepository utilisateurRepository,
PartageRepository partageRepository) {
this.photoRepository = photoRepository; this.photoRepository = photoRepository;
this.utilisateurRepository = utilisateurRepository; this.utilisateurRepository = utilisateurRepository;
this.partageRepository = partageRepository; this.partageRepository = partageRepository;
} }
//============= STORE PHOTO =============================
@Override @Override
public Photo store(MultipartFile file, String visibilite, String ownerEmail) throws IOException { public PhotoDTO store(MultipartFile file, String visibilite, String ownerEmail) {
if (file.isEmpty()) throw new IOException("Fichier vide"); try {
if (file.isEmpty()) throw new IOException("Fichier vide");
// Vérifier taille / type si besoin (ici basique) String original = StringUtils.cleanPath(file.getOriginalFilename());
String original = StringUtils.cleanPath(file.getOriginalFilename()); String uuid = UUID.randomUUID() + "-" + original;
String uuid = UUID.randomUUID().toString() + "-" + original; Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath))
Path uploadPath = Paths.get(uploadDir); Files.createDirectories(uploadPath);
if (!Files.exists(uploadPath)) Files.createDirectories(uploadPath); Files.copy(file.getInputStream(),
uploadPath.resolve(uuid),
Path target = uploadPath.resolve(uuid); StandardCopyOption.REPLACE_EXISTING);
Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING); Utilisateur owner = utilisateurRepository.findByEmail(ownerEmail)
.orElseThrow(() -> new RuntimeException("Utilisateur introuvable"));
Photo p = new Photo(); Photo p = new Photo();
p.setNomFichierOriginal(original); p.setNomFichierOriginal(original);
p.setUuidFichier(uuid); p.setUuidFichier(uuid);
p.setDateUpload(LocalDateTime.now()); p.setVisibilite(Photo.Visibilite.valueOf(visibilite));
p.setVisibilite(Photo.Visibilite.valueOf(visibilite)); p.setDateUpload(LocalDateTime.now());
Utilisateur u = utilisateurRepository.findByEmail(ownerEmail).orElseThrow(() -> new IOException("Utilisateur introuvable")); p.setProprietaire(owner);
p.setProprietaire(u); return PhotoMapper.toDTO(photoRepository.save(p));
} catch (Exception ex) {
return photoRepository.save(p); throw new RuntimeException("Erreur upload : " + ex.getMessage(), ex);
}
} }
@Override
public Path loadAsPath(String uuidFile) {
return Paths.get(uploadDir).resolve(uuidFile);
}
@Override
public List<Photo> listByOwner(String email) {
return photoRepository.findByProprietaire_Email(email);
}
//============= GET PHOTO BY ID ==========================
@Override @Override
public List<Photo> listPublicPhotos() { public PhotoDTO getPhotoById(Long id) {
return photoRepository.findByVisibilite(Photo.Visibilite.PUBLIC); return photoRepository.findById(id)
} .map(PhotoMapper::toDTO)
@Override
public List<Photo> listSharedWith(String email) {
List<Partage> partages = partageRepository.findByUtilisateur_Email(email);
return partages.stream()
.map(Partage::getPhoto)
.toList();
}
@Override
public List<Photo> listPrivatePhotos(String email) {
return photoRepository.findByProprietaire_Email(email).stream()
.filter(p -> p.getVisibilite() == Photo.Visibilite.PRIVATE)
.toList();
}
@Override
public List<Photo> listPublicPhotos(String email) {
return photoRepository.findByProprietaire_Email(email).stream()
.filter(p -> p.getVisibilite() == Photo.Visibilite.PUBLIC)
.toList();
}
@Override
public void unshare(Long photoId, String email) {
Partage p = partageRepository.findByPhoto_Id(photoId).stream()
.filter(x -> x.getUtilisateur().getEmail().equals(email))
.findFirst()
.orElse(null); .orElse(null);
if (p != null) partageRepository.delete(p);
} }
//============= LOAD RESOURCE ============================
@Override @Override
public Page<Photo> listPublic(int page, int size) { public Resource loadAsResource(String uuidFichier) {
try {
Path file = Paths.get(uploadDir).resolve(uuidFichier);
Resource resource = new UrlResource(file.toUri());
if (!resource.exists() || !resource.isReadable())
throw new RuntimeException("Impossible de lire le fichier : " + uuidFichier);
return resource;
} catch (MalformedURLException e) {
throw new RuntimeException("Fichier introuvable : " + uuidFichier);
}
}
//============= LIST PUBLIC PHOTOS =======================
@Override
public Page<PhotoDTO> listPublic(int page, int size) {
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
return photoRepository.findByVisibilite(Photo.Visibilite.PUBLIC, pageable); return photoRepository
.findByVisibilite(Photo.Visibilite.PUBLIC, pageable)
.map(PhotoMapper::toDTO);
} }
//============= PRIVATE PHOTOS OF USER ===================
@Override @Override
public Page<Photo> listPrivatePhotos(String email, int page, int size) { public Page<PhotoDTO> listPrivatePhotos(String email, int page, int size) {
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
return photoRepository.findByProprietaire_EmailAndVisibilite( return photoRepository
email, .findByProprietaire_EmailAndVisibilite(email, Photo.Visibilite.PRIVATE, pageable)
Photo.Visibilite.PRIVATE, .map(PhotoMapper::toDTO);
pageable
);
} }
//============= PUBLIC PHOTOS OF USER ====================
@Override @Override
public Page<Photo> listPublicPhotos(String email, int page, int size) { public Page<PhotoDTO> listPublicPhotos(String email, int page, int size) {
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
return photoRepository.findByProprietaire_EmailAndVisibilite( return photoRepository
email, .findByProprietaire_EmailAndVisibilite(email, Photo.Visibilite.PUBLIC, pageable)
Photo.Visibilite.PUBLIC, .map(PhotoMapper::toDTO);
pageable
);
} }
//============= PHOTOS SHARED WITH USER ==================
@Override @Override
public Page<Photo> listSharedWith(String email, int page, int size) { public Page<PhotoDTO> listSharedWith(String email, int page, int size) {
List<Partage> partages = partageRepository.findByUtilisateur_Email(email); List<PhotoDTO> photos =
List<Photo> photos = partages.stream().map(Partage::getPhoto).toList(); partageRepository.findByUtilisateur_Email(email)
// convertir list en page manuellement .stream()
.map(Partage::getPhoto)
.map(PhotoMapper::toDTO)
.toList();
int start = page * size; int start = page * size;
int end = Math.min(start + size, photos.size()); int end = Math.min(start + size, photos.size());
List<Photo> sublist = photos.subList(start, end); List<PhotoDTO> sub = photos.subList(start, end);
return new PageImpl<>(sublist, PageRequest.of(page, size), photos.size()); return new PageImpl<>(sub, PageRequest.of(page, size), photos.size());
} }
//============= PHOTOS SET AS SHARED BY USER =============
@Override @Override
public Page<Photo> listSharedPhotos(String email, int page, int size) { public Page<PhotoDTO> listSharedPhotos(String email, int page, int size) {
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
return photoRepository.findByProprietaire_EmailAndVisibilite( return photoRepository
email, .findByProprietaire_EmailAndVisibilite(email, Photo.Visibilite.SHARED, pageable)
Photo.Visibilite.SHARED, .map(PhotoMapper::toDTO);
pageable
);
} }
}
}

View File

@@ -0,0 +1,30 @@
package local.epul4a.fotosharing.service.impl;
import local.epul4a.fotosharing.dto.UtilisateurDTO;
import local.epul4a.fotosharing.mapper.UtilisateurMapper;
import local.epul4a.fotosharing.model.Utilisateur;
import local.epul4a.fotosharing.repository.UtilisateurRepository;
import local.epul4a.fotosharing.service.UtilisateurService;
import org.springframework.stereotype.Service;
@Service
public class UtilisateurServiceImpl implements UtilisateurService {
private final UtilisateurRepository utilisateurRepository;
public UtilisateurServiceImpl(UtilisateurRepository utilisateurRepository) {
this.utilisateurRepository = utilisateurRepository;
}
@Override
public UtilisateurDTO findByEmail(String email) {
Utilisateur u = utilisateurRepository.findByEmail(email)
.orElse(null);
return UtilisateurMapper.toDTO(u);
}
@Override
public boolean existsByEmail(String email) {
return utilisateurRepository.findByEmail(email).isPresent();
}
}

View File

@@ -8,9 +8,6 @@
<h2>Mes photos</h2> <h2>Mes photos</h2>
<p><a th:href="@{/upload}">Uploader une photo</a></p> <p><a th:href="@{/upload}">Uploader une photo</a></p>
<p><a th:href="@{/}">Retour accueil</a></p> <p><a th:href="@{/}">Retour accueil</a></p>
<div th:if="${#lists.isEmpty(mesPhotos)}">
<p>Vous n'avez pas encore de photos.</p>
</div>
<h2>Mes photos privées</h2> <h2>Mes photos privées</h2>
<div> <div>
<a th:each="p : ${photosPrivees.content}" <a th:each="p : ${photosPrivees.content}"
@@ -52,10 +49,21 @@
</div> </div>
<div> <div>
<a th:if="${mesPhotosPartagees.hasPrevious()}" <a th:if="${mesPhotosPartagees.hasPrevious()}"
th:href="@{/mes-photos(pageMesPartagees=${pageMesPartagees - 1})}"></a> th:href="@{/mes-photos(
pagePrivees=${pagePrivees},
pagePubliques=${pagePubliques},
pagePartagees=${pagePartagees},
pageMesPartagees=${pageMesPartagees - 1}
)}"></a>
<span th:text="'Page ' + (${pageMesPartagees}+1)"></span> <span th:text="'Page ' + (${pageMesPartagees}+1)"></span>
<a th:if="${mesPhotosPartagees.hasNext()}" <a th:if="${mesPhotosPartagees.hasNext()}"
th:href="@{/mes-photos(pageMesPartagees=${pageMesPartagees + 1})}"></a> th:href="@{/mes-photos(
pagePrivees=${pagePrivees},
pagePubliques=${pagePubliques},
pagePartagees=${pagePartagees},
pageMesPartagees=${pageMesPartagees + 1}
)}"></a>
</div> </div>

View File

@@ -34,6 +34,8 @@
<ul> <ul>
<li th:each="p : ${partages}"> <li th:each="p : ${partages}">
<span th:text="${p.utilisateur.email}"></span> <span th:text="${p.utilisateur.email}"></span>
<span th:text="${p.permission}"></span>
<a th:href="@{'/photo/' + ${photo.id} + '/unshare/' + ${p.utilisateur.email}}" <a th:href="@{'/photo/' + ${photo.id} + '/unshare/' + ${p.utilisateur.email}}"
style="color:red;">Retirer</a> style="color:red;">Retirer</a>
</li> </li>
@@ -43,6 +45,12 @@
<form th:action="@{'/photo/' + ${photo.id} + '/share'}" method="post"> <form th:action="@{'/photo/' + ${photo.id} + '/share'}" method="post">
<label>Email de l'utilisateur :</label> <label>Email de l'utilisateur :</label>
<input type="email" name="email" required /> <input type="email" name="email" required />
<label>Permission :</label>
<select name="permission">
<option value="READ">Lecture</option>
<option value="COMMENT">Commenter</option>
<option value="ADMIN">Administrer</option>
</select>
<button type="submit">Partager</button> <button type="submit">Partager</button>
</form> </form>
</div> </div>

View File

@@ -8,9 +8,6 @@
<h2>Mes photos</h2> <h2>Mes photos</h2>
<p><a th:href="@{/upload}">Uploader une photo</a></p> <p><a th:href="@{/upload}">Uploader une photo</a></p>
<p><a th:href="@{/}">Retour accueil</a></p> <p><a th:href="@{/}">Retour accueil</a></p>
<div th:if="${#lists.isEmpty(mesPhotos)}">
<p>Vous n'avez pas encore de photos.</p>
</div>
<h2>Mes photos privées</h2> <h2>Mes photos privées</h2>
<div> <div>
<a th:each="p : ${photosPrivees.content}" <a th:each="p : ${photosPrivees.content}"
@@ -52,10 +49,21 @@
</div> </div>
<div> <div>
<a th:if="${mesPhotosPartagees.hasPrevious()}" <a th:if="${mesPhotosPartagees.hasPrevious()}"
th:href="@{/mes-photos(pageMesPartagees=${pageMesPartagees - 1})}"></a> th:href="@{/mes-photos(
pagePrivees=${pagePrivees},
pagePubliques=${pagePubliques},
pagePartagees=${pagePartagees},
pageMesPartagees=${pageMesPartagees - 1}
)}"></a>
<span th:text="'Page ' + (${pageMesPartagees}+1)"></span> <span th:text="'Page ' + (${pageMesPartagees}+1)"></span>
<a th:if="${mesPhotosPartagees.hasNext()}" <a th:if="${mesPhotosPartagees.hasNext()}"
th:href="@{/mes-photos(pageMesPartagees=${pageMesPartagees + 1})}"></a> th:href="@{/mes-photos(
pagePrivees=${pagePrivees},
pagePubliques=${pagePubliques},
pagePartagees=${pagePartagees},
pageMesPartagees=${pageMesPartagees + 1}
)}"></a>
</div> </div>

View File

@@ -34,6 +34,8 @@
<ul> <ul>
<li th:each="p : ${partages}"> <li th:each="p : ${partages}">
<span th:text="${p.utilisateur.email}"></span> <span th:text="${p.utilisateur.email}"></span>
<span th:text="${p.permission}"></span>
<a th:href="@{'/photo/' + ${photo.id} + '/unshare/' + ${p.utilisateur.email}}" <a th:href="@{'/photo/' + ${photo.id} + '/unshare/' + ${p.utilisateur.email}}"
style="color:red;">Retirer</a> style="color:red;">Retirer</a>
</li> </li>
@@ -43,6 +45,12 @@
<form th:action="@{'/photo/' + ${photo.id} + '/share'}" method="post"> <form th:action="@{'/photo/' + ${photo.id} + '/share'}" method="post">
<label>Email de l'utilisateur :</label> <label>Email de l'utilisateur :</label>
<input type="email" name="email" required /> <input type="email" name="email" required />
<label>Permission :</label>
<select name="permission">
<option value="READ">Lecture</option>
<option value="COMMENT">Commenter</option>
<option value="ADMIN">Administrer</option>
</select>
<button type="submit">Partager</button> <button type="submit">Partager</button>
</form> </form>
</div> </div>