FEAT : Mise en place de l'architecture + instription d'un utilisateur
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -31,4 +31,4 @@ build/
|
|||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
.env
|
src/main/resources/application.properties
|
||||||
|
|||||||
9
pom.xml
9
pom.xml
@@ -83,6 +83,15 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package local.epul4a.fotosharing.controller;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Utilisateur;
|
||||||
|
import local.epul4a.fotosharing.repository.UtilisateurRepository;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final UtilisateurRepository utilisateurRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public AuthController(UtilisateurRepository utilisateurRepository, PasswordEncoder passwordEncoder) {
|
||||||
|
this.utilisateurRepository = utilisateurRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public String loginPage(@RequestParam(value = "error", required = false) String error,
|
||||||
|
@RequestParam(value = "logout", required = false) String logout,
|
||||||
|
Model model) {
|
||||||
|
model.addAttribute("error", error != null);
|
||||||
|
model.addAttribute("logout", logout != null);
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/register")
|
||||||
|
public String registerForm(Model model) {
|
||||||
|
model.addAttribute("utilisateur", new Utilisateur());
|
||||||
|
return "register";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public String doRegister(@ModelAttribute("utilisateur") @Valid Utilisateur utilisateur,
|
||||||
|
BindingResult bindingResult, Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
return "register";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utilisateurRepository.findByEmail(utilisateur.getEmail()).isPresent()) {
|
||||||
|
model.addAttribute("error", "Email déjà utilisé");
|
||||||
|
return "register";
|
||||||
|
}
|
||||||
|
|
||||||
|
// encoder le mot de passe puis sauvegarder
|
||||||
|
utilisateur.setMotDePasse(passwordEncoder.encode(utilisateur.getMotDePasse()));
|
||||||
|
utilisateur.setActif(true);
|
||||||
|
utilisateurRepository.save(utilisateur);
|
||||||
|
return "redirect:/login?registered";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package local.epul4a.fotosharing.controller;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Photo;
|
||||||
|
import local.epul4a.fotosharing.security.CustomUserDetails;
|
||||||
|
import local.epul4a.fotosharing.service.PhotoService;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.PathResource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class PhotoController {
|
||||||
|
|
||||||
|
private final PhotoService photoService;
|
||||||
|
|
||||||
|
public PhotoController(PhotoService photoService) {
|
||||||
|
this.photoService = photoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String home() {
|
||||||
|
return "home"; // créer une page home.html simple
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/upload")
|
||||||
|
public String uploadForm() {
|
||||||
|
return "upload";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public String doUpload(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value="visibilite", defaultValue = "PRIVATE") String visibilite,
|
||||||
|
@AuthenticationPrincipal CustomUserDetails user,
|
||||||
|
Model model) {
|
||||||
|
try {
|
||||||
|
Photo p = photoService.store(file, visibilite, user.getUsername());
|
||||||
|
model.addAttribute("message", "Upload OK : " + p.getId());
|
||||||
|
return "redirect:/"; // ou page d'affichage
|
||||||
|
} catch (Exception e) {
|
||||||
|
model.addAttribute("error", e.getMessage());
|
||||||
|
return "upload";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/photo/{id}/raw")
|
||||||
|
public ResponseEntity<Resource> rawPhoto(@PathVariable("id") String idOrUuid) {
|
||||||
|
// idOrUuid peut être uuid stocké ou id numeric ; ici on assume uuid
|
||||||
|
Path p = photoService.loadAsPath(idOrUuid);
|
||||||
|
Resource r = new PathResource(p);
|
||||||
|
if (!r.exists()) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
String contentType = "application/octet-stream";
|
||||||
|
try {
|
||||||
|
// tentative de détection basique
|
||||||
|
contentType = java.nio.file.Files.probeContentType(p);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(contentType != null ? contentType : "application/octet-stream"))
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + p.getFileName().toString() + "\"")
|
||||||
|
.body(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package local.epul4a.fotosharing.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class Commentaire {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String contenu;
|
||||||
|
|
||||||
|
private LocalDateTime dateCommentaire;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "id_photo")
|
||||||
|
private Photo photo;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "id_utilisateur")
|
||||||
|
private Utilisateur auteur;
|
||||||
|
|
||||||
|
// Getters & Setters
|
||||||
|
}
|
||||||
25
src/main/java/local/epul4a/fotosharing/model/Partage.java
Normal file
25
src/main/java/local/epul4a/fotosharing/model/Partage.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package local.epul4a.fotosharing.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class Partage {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "id_photo")
|
||||||
|
private Photo photo;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "id_utilisateur")
|
||||||
|
private Utilisateur utilisateur;
|
||||||
|
|
||||||
|
// Getters & Setters
|
||||||
|
}
|
||||||
32
src/main/java/local/epul4a/fotosharing/model/Photo.java
Normal file
32
src/main/java/local/epul4a/fotosharing/model/Photo.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package local.epul4a.fotosharing.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class Photo {
|
||||||
|
public enum Visibilite { PRIVATE, PUBLIC, SHARED }
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String nomFichierOriginal;
|
||||||
|
private String uuidFichier;
|
||||||
|
private LocalDateTime dateUpload;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private Visibilite visibilite;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "id_utilisateur")
|
||||||
|
private Utilisateur proprietaire;
|
||||||
|
|
||||||
|
// getters & setters
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package local.epul4a.fotosharing.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class Utilisateur {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String motDePasse;
|
||||||
|
|
||||||
|
private String nom;
|
||||||
|
|
||||||
|
private String prenom;
|
||||||
|
|
||||||
|
private boolean actif = true;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "proprietaire")
|
||||||
|
private List<
|
||||||
|
Photo> photos;
|
||||||
|
|
||||||
|
// Getters & Setters avec lombok
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package local.epul4a.fotosharing.repository;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Photo;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface PhotoRepository extends JpaRepository<Photo, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package local.epul4a.fotosharing.repository;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Utilisateur;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface UtilisateurRepository extends JpaRepository<Utilisateur, Long> {
|
||||||
|
Optional<Utilisateur> findByEmail(String email);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package local.epul4a.fotosharing.security;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Utilisateur;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class CustomUserDetails implements UserDetails {
|
||||||
|
|
||||||
|
private final Utilisateur user;
|
||||||
|
|
||||||
|
public CustomUserDetails(Utilisateur user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Utilisateur getUtilisateur() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
// Si tu veux ajouter des rôles, adapte ici.
|
||||||
|
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return user.getMotDePasse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return user.getEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isAccountNonExpired() { return true; }
|
||||||
|
@Override public boolean isAccountNonLocked() { return true; }
|
||||||
|
@Override public boolean isCredentialsNonExpired() { return true; }
|
||||||
|
@Override public boolean isEnabled() { return user.isActif(); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package local.epul4a.fotosharing.security;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Utilisateur;
|
||||||
|
import local.epul4a.fotosharing.repository.UtilisateurRepository;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
private final UtilisateurRepository utilisateurRepository;
|
||||||
|
|
||||||
|
public CustomUserDetailsService(UtilisateurRepository utilisateurRepository) {
|
||||||
|
this.utilisateurRepository = utilisateurRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
Utilisateur u = utilisateurRepository.findByEmail(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("Utilisateur introuvable: " + username));
|
||||||
|
return new CustomUserDetails(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package local.epul4a.fotosharing.security;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableMethodSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final CustomUserDetailsService customUserDetailsService;
|
||||||
|
|
||||||
|
public SecurityConfig(CustomUserDetailsService customUserDetailsService) {
|
||||||
|
this.customUserDetailsService = customUserDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.formLogin(form -> form
|
||||||
|
.loginPage("/login")
|
||||||
|
.defaultSuccessUrl("/", true)
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.logout(logout -> logout
|
||||||
|
.logoutSuccessUrl("/login?logout=true")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.csrf(csrf -> csrf.disable());
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService() {
|
||||||
|
// Spring Security 6.2 utilise ce bean automatiquement
|
||||||
|
return customUserDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
// Spring Boot crée automatiquement un DaoAuthenticationProvider interne
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package local.epul4a.fotosharing.security;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Photo;
|
||||||
|
import local.epul4a.fotosharing.repository.PhotoRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service("securityService")
|
||||||
|
public class SecurityService {
|
||||||
|
|
||||||
|
private final PhotoRepository photoRepository;
|
||||||
|
|
||||||
|
public SecurityService(PhotoRepository photoRepository) {
|
||||||
|
this.photoRepository = photoRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canAccessPhoto(org.springframework.security.core.Authentication authentication, Long photoId) {
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) return false;
|
||||||
|
String username = authentication.getName(); // email
|
||||||
|
Optional<Photo> p = photoRepository.findById(photoId);
|
||||||
|
if (p.isEmpty()) return false;
|
||||||
|
Photo photo = p.get();
|
||||||
|
if (photo.getVisibilite() == Photo.Visibilite.PUBLIC) return true;
|
||||||
|
if (photo.getProprietaire() != null && photo.getProprietaire().getEmail().equals(username)) return true;
|
||||||
|
// TODO: vérifier table partage
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package local.epul4a.fotosharing.service;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Photo;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public interface PhotoService {
|
||||||
|
Photo store(MultipartFile file, String visibilite, String ownerEmail) throws IOException;
|
||||||
|
Path loadAsPath(String uuidFile);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package local.epul4a.fotosharing.service.impl;
|
||||||
|
|
||||||
|
import local.epul4a.fotosharing.model.Photo;
|
||||||
|
import local.epul4a.fotosharing.model.Utilisateur;
|
||||||
|
import local.epul4a.fotosharing.repository.PhotoRepository;
|
||||||
|
import local.epul4a.fotosharing.repository.UtilisateurRepository;
|
||||||
|
import local.epul4a.fotosharing.service.PhotoService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PhotoServiceImpl implements PhotoService {
|
||||||
|
|
||||||
|
@Value("${file.upload-dir}")
|
||||||
|
private String uploadDir;
|
||||||
|
|
||||||
|
private final PhotoRepository photoRepository;
|
||||||
|
private final UtilisateurRepository utilisateurRepository;
|
||||||
|
|
||||||
|
public PhotoServiceImpl(PhotoRepository photoRepository, UtilisateurRepository utilisateurRepository) {
|
||||||
|
this.photoRepository = photoRepository;
|
||||||
|
this.utilisateurRepository = utilisateurRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Photo store(MultipartFile file, String visibilite, String ownerEmail) throws IOException {
|
||||||
|
if (file.isEmpty()) throw new IOException("Fichier vide");
|
||||||
|
|
||||||
|
// Vérifier taille / type si besoin (ici basique)
|
||||||
|
String original = StringUtils.cleanPath(file.getOriginalFilename());
|
||||||
|
String uuid = UUID.randomUUID().toString() + "-" + original;
|
||||||
|
|
||||||
|
Path uploadPath = Paths.get(uploadDir);
|
||||||
|
if (!Files.exists(uploadPath)) Files.createDirectories(uploadPath);
|
||||||
|
|
||||||
|
Path target = uploadPath.resolve(uuid);
|
||||||
|
Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
Photo p = new Photo();
|
||||||
|
p.setNomFichierOriginal(original);
|
||||||
|
p.setUuidFichier(uuid);
|
||||||
|
p.setDateUpload(LocalDateTime.now());
|
||||||
|
p.setVisibilite(Photo.Visibilite.valueOf(visibilite));
|
||||||
|
Utilisateur u = utilisateurRepository.findByEmail(ownerEmail).orElseThrow(() -> new IOException("Utilisateur introuvable"));
|
||||||
|
p.setProprietaire(u);
|
||||||
|
|
||||||
|
return photoRepository.save(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path loadAsPath(String uuidFile) {
|
||||||
|
return Paths.get(uploadDir).resolve(uuidFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ spring.application.name=FotoSharing
|
|||||||
# ===============================
|
# ===============================
|
||||||
# DATABASE
|
# DATABASE
|
||||||
# ===============================
|
# ===============================
|
||||||
spring.datasource.url=jdbc:mariadb://192.168.124.171:3306/fotoshareDB
|
spring.datasource.url=jdbc:mariadb://192.168.112.10:3306/fotoshareDB
|
||||||
spring.datasource.username=ufoto
|
spring.datasource.username=ufoto
|
||||||
spring.datasource.password=4AinfoRep-25
|
spring.datasource.password=4AinfoRep-25
|
||||||
# ===============================
|
# ===============================
|
||||||
@@ -12,6 +12,14 @@ spring.datasource.password=4AinfoRep-25
|
|||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
spring.thymeleaf.prefix=classpath:/templates/
|
spring.thymeleaf.prefix=classpath:/templates/
|
||||||
spring.thymeleaf.suffix=.html
|
spring.thymeleaf.suffix=.html
|
||||||
spring.thymeleaf.mode=HTML
|
spring.thymeleaf.mode=HTML
|
||||||
|
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
|
||||||
|
# ===============================
|
||||||
|
# EMPLACEMENT DE STICKAGE
|
||||||
|
# ===============================
|
||||||
|
file.upload-dir=/opt/photo-app/uploads
|
||||||
|
spring.servlet.multipart.max-file-size=20MB
|
||||||
|
spring.servlet.multipart.max-request-size=20MB
|
||||||
9
src/main/resources/templates/home.html
Normal file
9
src/main/resources/templates/home.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head><meta charset="utf-8"/><title>Accueil</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Bienvenue sur FotoSharing</h1>
|
||||||
|
<p><a th:href="@{/upload}">Uploader une photo</a></p>
|
||||||
|
<p><a th:href="@{/logout}">Se déconnecter</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
src/main/resources/templates/login.html
Normal file
23
src/main/resources/templates/login.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Login - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Connexion</h1>
|
||||||
|
<form th:action="@{/login}" method="post">
|
||||||
|
<label>Email: <input type="text" name="username"/></label><br/>
|
||||||
|
<label>Mot de passe: <input type="password" name="password"/></label><br/>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div th:if="${param.logout}">
|
||||||
|
Déconnecté avec succès.
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.error}">
|
||||||
|
Erreur d'authentification.
|
||||||
|
</div>
|
||||||
|
<p><a th:href="@{/register}">Créer un compte</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
src/main/resources/templates/register.html
Normal file
17
src/main/resources/templates/register.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Inscription - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Inscription</h1>
|
||||||
|
<form th:action="@{/register}" th:object="${utilisateur}" method="post">
|
||||||
|
<label>Email: <input th:field="*{email}" /></label><br/>
|
||||||
|
<label>Nom: <input th:field="*{nom}" /></label><br/>
|
||||||
|
<label>Prénom: <input th:field="*{prenom}" /></label><br/>
|
||||||
|
<label>Mot de passe: <input th:field="*{motDePasse}" type="password"/></label><br/>
|
||||||
|
<button type="submit">Créer</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
src/main/resources/templates/upload.html
Normal file
23
src/main/resources/templates/upload.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Upload - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Uploader une photo</h1>
|
||||||
|
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file"/><br/>
|
||||||
|
<label>Visibilité:
|
||||||
|
<select name="visibilite">
|
||||||
|
<option value="PRIVATE">Privée</option>
|
||||||
|
<option value="PUBLIC">Publique</option>
|
||||||
|
<option value="SHARED">Partagée</option>
|
||||||
|
</select>
|
||||||
|
</label><br/>
|
||||||
|
<button type="submit">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
<div th:if="${error}" th:text="${error}"></div>
|
||||||
|
<div th:if="${message}" th:text="${message}"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,7 +3,7 @@ spring.application.name=FotoSharing
|
|||||||
# ===============================
|
# ===============================
|
||||||
# DATABASE
|
# DATABASE
|
||||||
# ===============================
|
# ===============================
|
||||||
spring.datasource.url=jdbc:mariadb://192.168.124.171:3306/fotoshareDB
|
spring.datasource.url=jdbc:mariadb://192.168.112.10:3306/fotoshareDB
|
||||||
spring.datasource.username=ufoto
|
spring.datasource.username=ufoto
|
||||||
spring.datasource.password=4AinfoRep-25
|
spring.datasource.password=4AinfoRep-25
|
||||||
# ===============================
|
# ===============================
|
||||||
@@ -12,6 +12,14 @@ spring.datasource.password=4AinfoRep-25
|
|||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
spring.thymeleaf.prefix=classpath:/templates/
|
spring.thymeleaf.prefix=classpath:/templates/
|
||||||
spring.thymeleaf.suffix=.html
|
spring.thymeleaf.suffix=.html
|
||||||
spring.thymeleaf.mode=HTML
|
spring.thymeleaf.mode=HTML
|
||||||
|
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
|
||||||
|
# ===============================
|
||||||
|
# EMPLACEMENT DE STICKAGE
|
||||||
|
# ===============================
|
||||||
|
file.upload-dir=/opt/photo-app/uploads
|
||||||
|
spring.servlet.multipart.max-file-size=20MB
|
||||||
|
spring.servlet.multipart.max-request-size=20MB
|
||||||
Binary file not shown.
Binary file not shown.
BIN
target/classes/local/epul4a/fotosharing/model/Commentaire.class
Normal file
BIN
target/classes/local/epul4a/fotosharing/model/Commentaire.class
Normal file
Binary file not shown.
BIN
target/classes/local/epul4a/fotosharing/model/Partage.class
Normal file
BIN
target/classes/local/epul4a/fotosharing/model/Partage.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/local/epul4a/fotosharing/model/Photo.class
Normal file
BIN
target/classes/local/epul4a/fotosharing/model/Photo.class
Normal file
Binary file not shown.
BIN
target/classes/local/epul4a/fotosharing/model/Utilisateur.class
Normal file
BIN
target/classes/local/epul4a/fotosharing/model/Utilisateur.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
9
target/classes/templates/home.html
Normal file
9
target/classes/templates/home.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head><meta charset="utf-8"/><title>Accueil</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Bienvenue sur FotoSharing</h1>
|
||||||
|
<p><a th:href="@{/upload}">Uploader une photo</a></p>
|
||||||
|
<p><a th:href="@{/logout}">Se déconnecter</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
target/classes/templates/login.html
Normal file
23
target/classes/templates/login.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Login - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Connexion</h1>
|
||||||
|
<form th:action="@{/login}" method="post">
|
||||||
|
<label>Email: <input type="text" name="username"/></label><br/>
|
||||||
|
<label>Mot de passe: <input type="password" name="password"/></label><br/>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div th:if="${param.logout}">
|
||||||
|
Déconnecté avec succès.
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.error}">
|
||||||
|
Erreur d'authentification.
|
||||||
|
</div>
|
||||||
|
<p><a th:href="@{/register}">Créer un compte</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
target/classes/templates/register.html
Normal file
17
target/classes/templates/register.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Inscription - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Inscription</h1>
|
||||||
|
<form th:action="@{/register}" th:object="${utilisateur}" method="post">
|
||||||
|
<label>Email: <input th:field="*{email}" /></label><br/>
|
||||||
|
<label>Nom: <input th:field="*{nom}" /></label><br/>
|
||||||
|
<label>Prénom: <input th:field="*{prenom}" /></label><br/>
|
||||||
|
<label>Mot de passe: <input th:field="*{motDePasse}" type="password"/></label><br/>
|
||||||
|
<button type="submit">Créer</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
target/classes/templates/upload.html
Normal file
23
target/classes/templates/upload.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Upload - FotoSharing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Uploader une photo</h1>
|
||||||
|
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file"/><br/>
|
||||||
|
<label>Visibilité:
|
||||||
|
<select name="visibilite">
|
||||||
|
<option value="PRIVATE">Privée</option>
|
||||||
|
<option value="PUBLIC">Publique</option>
|
||||||
|
<option value="SHARED">Partagée</option>
|
||||||
|
</select>
|
||||||
|
</label><br/>
|
||||||
|
<button type="submit">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
<div th:if="${error}" th:text="${error}"></div>
|
||||||
|
<div th:if="${message}" th:text="${message}"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user