From 145584a28a444bb367201ae25b8fac148dca712c Mon Sep 17 00:00:00 2001 From: Paul <paul.cancel.etu@univ-lille.fr> Date: Sat, 18 Jan 2025 13:02:40 +0100 Subject: [PATCH] Application sans le calendrier --- pom.xml | 8 +- .../MeetingPlannr/MeetingController.java | 252 ++++++++++-- .../MeetingPlannrApplication.java | 2 +- .../infoetu/MeetingPlannr/pojo/Meeting.java | 5 +- .../infoetu/MeetingPlannr/pojo/Request.java | 13 +- .../but/infoetu/MeetingPlannr/pojo/User.java | 73 +++- .../repository/MeetingRepository.java | 8 +- .../repository/RequestRepository.java | 4 +- .../repository/UserRepository.java | 6 +- .../MeetingPlannr/security/Security.java | 93 +++++ .../MeetingPlannr/service/UserService.java | 22 ++ src/main/resources/application.properties | 22 +- src/main/resources/import.sql | 44 ++- src/main/resources/static/styles/main.css | 361 ++++++++++++++++++ src/main/webapp/WEB-INF/jsp/common/footer.jsp | 6 + src/main/webapp/WEB-INF/jsp/common/header.jsp | 13 + src/main/webapp/WEB-INF/jsp/listeAction.jsp | 81 ---- src/main/webapp/WEB-INF/jsp/login.jsp | 88 ----- src/main/webapp/WEB-INF/jsp/public/login.jsp | 45 +++ .../WEB-INF/jsp/{ => public}/register.jsp | 59 +-- .../WEB-INF/jsp/user/detailRendezVous.jsp | 31 ++ .../webapp/WEB-INF/jsp/user/editMeeting.jsp | 45 +++ .../webapp/WEB-INF/jsp/user/editProfile.jsp | 40 ++ .../webapp/WEB-INF/jsp/user/listeAction.jsp | 38 ++ src/main/webapp/WEB-INF/jsp/user/profile.jsp | 29 ++ .../webapp/WEB-INF/jsp/user/rendezVous.jsp | 46 +++ .../MeetingPlannrApplicationTests.java | 2 +- 27 files changed, 1143 insertions(+), 293 deletions(-) create mode 100644 src/main/java/fr/but/infoetu/MeetingPlannr/security/Security.java create mode 100644 src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java create mode 100644 src/main/resources/static/styles/main.css create mode 100644 src/main/webapp/WEB-INF/jsp/common/footer.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/common/header.jsp delete mode 100644 src/main/webapp/WEB-INF/jsp/listeAction.jsp delete mode 100644 src/main/webapp/WEB-INF/jsp/login.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/public/login.jsp rename src/main/webapp/WEB-INF/jsp/{ => public}/register.jsp (54%) create mode 100644 src/main/webapp/WEB-INF/jsp/user/detailRendezVous.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/user/editMeeting.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/user/editProfile.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/user/listeAction.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/user/profile.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/user/rendezVous.jsp diff --git a/pom.xml b/pom.xml index 17b76ca..4a6eede 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>fr.but.infoetu</groupId> - <artifactId>MeetingPlannr</artifactId> + <artifactId>meetingplannr</artifactId> <version>0.0.1-SNAPSHOT</version> - <name>MeetingPlannr</name> + <name>meetingplannr</name> <description>Demo project for Spring Boot</description> <url/> <licenses> @@ -69,6 +69,10 @@ <artifactId>tomcat-embed-jasper</artifactId> <scope>compile</scope> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> </dependencies> <build> diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingController.java b/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingController.java index 5437580..0a4606e 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingController.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingController.java @@ -1,19 +1,29 @@ -package fr.but.infoetu.MeetingPlannr; +package fr.but.infoetu.meetingplannr; +import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import fr.but.infoetu.MeetingPlannr.pojo.User; -import fr.but.infoetu.MeetingPlannr.repository.MeetingRepository; -import fr.but.infoetu.MeetingPlannr.repository.RequestRepository; -import fr.but.infoetu.MeetingPlannr.repository.UserRepository; +import fr.but.infoetu.meetingplannr.pojo.Meeting; +import fr.but.infoetu.meetingplannr.pojo.User; +import fr.but.infoetu.meetingplannr.repository.MeetingRepository; +import fr.but.infoetu.meetingplannr.repository.RequestRepository; +import fr.but.infoetu.meetingplannr.repository.UserRepository; +import fr.but.infoetu.meetingplannr.service.UserService; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; @@ -27,48 +37,220 @@ public class MeetingController { @Autowired RequestRepository rr; - @RequestMapping(value = "login", method = RequestMethod.GET) - private String loginForm(){ - return "login"; + @Autowired + UserService userService; + + @RequestMapping + private String redirectHome(HttpSession session) { + if (session.getAttribute("currentUser") == null) { + return "public/login"; + } + return "user/listeAction"; } - @RequestMapping(value = "login", method = RequestMethod.POST) - private String loginFormPost(String email, String password, HttpSession session){ - Optional<User> userOpt = ur.findByEmail(email); - if(userOpt.isPresent()){ - User user = userOpt.get(); - if(user.getPassword().equals(password)){ - session.setAttribute("currentUser", user); - System.out.println("Bienvenue " + user.getSurname()); - return "listeAction"; - } - } - return "login"; + @RequestMapping(value = "public/login", method = RequestMethod.GET) + private String loginForm(){ + System.out.println("Is authenticated : " + isAuthenticated()); + if (isAuthenticated()) { + return "redirect:/user/listeAction"; + } + return "public/login"; } - @RequestMapping(value = "register", method = RequestMethod.GET) + @RequestMapping(value = "public/register", method = RequestMethod.GET) private String registerForm(){ - return "register"; + if (isAuthenticated()) { + return "redirect:/user/listeAction"; + } + return "public/register"; } - @RequestMapping(value = "register", method = RequestMethod.POST) - private String registerFormPost(@Valid User user, BindingResult res, Model model, HttpSession session){ - System.out.println(user); - if(res.hasErrors()){ - return "register"; + @RequestMapping(value = "public/register", method = RequestMethod.POST) + private String registerFormPost(@Valid User user, BindingResult res, Model model, HttpSession session) { + if (res.hasErrors()) { + StringBuilder errorMessage = new StringBuilder("Veuillez corriger les erreurs suivantes :<br>"); + res.getFieldErrors().forEach(error -> errorMessage.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("<br>")); + model.addAttribute("errorMessage", errorMessage.toString()); + return "public/register"; } - Optional<User> userOpt = ur.findByEmail(user.getEmail()); - if(userOpt.isPresent()){ - return "login"; + Optional<User> userOpt = ur.findByUsername(user.getUsername()); + if (userOpt.isPresent()) { + model.addAttribute("errorMessage", "Cet email est déjà utilisé."); + return "public/register"; } - ur.save(user); + userService.createUser(user); session.setAttribute("currentUser", user); - return "listeAction"; + // Automatically log in the user + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authToken); + + return "redirect:/user/listeAction"; + } + + @RequestMapping(value = "user/listeAction", method = RequestMethod.GET) + private String listeAction(@AuthenticationPrincipal UserDetails details, Model model){ + if (details == null) { + return "redirect:/public/login"; + } + User user = ur.findByUsername(details.getUsername()).get(); + model.addAttribute("currentUser", user); + return "user/listeAction"; + } + + @RequestMapping(value = "user/profile", method = RequestMethod.GET) + private String profile(@AuthenticationPrincipal UserDetails details, Model model) { + if (details == null) { + return "redirect:/public/login"; + } + User user = ur.findByUsername(details.getUsername()).get(); + model.addAttribute("currentUser", user); + return "user/profile"; + } + + @RequestMapping(value = "user/editProfile", method = RequestMethod.GET) + private String editProfile(@AuthenticationPrincipal UserDetails details, Model model) { + if (details == null) { + return "redirect:/public/login"; + } + User user = ur.findByUsername(details.getUsername()).get(); + model.addAttribute("currentUser", user); + return "user/editProfile"; + } + + + @RequestMapping(value = "user/updateProfile", method = RequestMethod.POST) + private String updateProfile(User user, BindingResult res, Model model, @AuthenticationPrincipal UserDetails details) { + System.out.println(user); + + if (res.hasErrors()) { + StringBuilder errorMessage = new StringBuilder("Veuillez corriger les erreurs suivantes :<br>"); + res.getFieldErrors().forEach(error -> errorMessage.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("<br>")); + model.addAttribute("errorMessage", errorMessage.toString()); + return "user/editProfile"; + } + + if (details == null) { + model.addAttribute("errorMessage", "Utilisateur non connecté."); + return "public/login"; + } + + User currentUser = ur.findByUsername(details.getUsername()).get(); + Optional<User> existingUser = ur.findByUsername(user.getUsername()); + if (existingUser.isPresent() && existingUser.get().getUno() != (currentUser.getUno())) { + model.addAttribute("errorMessage", "Cet email est déjà utilisé."); + return "user/editProfile"; + } + + currentUser.setName(user.getName()); + currentUser.setSurname(user.getSurname()); + + ur.save(currentUser); + + return "redirect:/user/profile"; + } + + @RequestMapping(value = "user/meetings", method = RequestMethod.GET) + private String meetings(@AuthenticationPrincipal UserDetails details, Model model) { + if (details == null) { + return "redirect:/public/login"; + } + User user = ur.findByUsername(details.getUsername()).get(); + List<Meeting> meetings = mr.findByUserOrderByDateAsc(user); + model.addAttribute("meetings", meetings); + model.addAttribute("currentUser", user); + return "user/rendezVous"; + } + + @RequestMapping(value = "user/meetings/detail/{mno}", method = RequestMethod.GET) + private String appointmentDetail(@PathVariable int mno, @AuthenticationPrincipal UserDetails details, Model model) { + if (details == null) { + return "redirect:/public/login"; + } + + Optional<Meeting> meetingOpt = mr.findById(mno); + if (!meetingOpt.isPresent()) { + return "redirect:/user/meetings"; + } + + Meeting meeting = meetingOpt.get(); + User currentUser = ur.findByUsername(details.getUsername()).get(); + + model.addAttribute("meeting", meeting); + model.addAttribute("isOwner", meeting.getUser().getUno() == currentUser.getUno()); + + return "user/detailRendezVous"; + } + + @RequestMapping(value = "user/meetings/edit/{mno}", method = RequestMethod.GET) + private String editMeeting(@PathVariable int mno, @AuthenticationPrincipal UserDetails details, Model model) { + Optional<Meeting> meetingOpt = mr.findById(mno); + if (!meetingOpt.isPresent()) { + return "redirect:/user/meetings"; + } + + Meeting meeting = meetingOpt.get(); + User currentUser = ur.findByUsername(details.getUsername()).get(); + + if (meeting.getUser().getUno() != currentUser.getUno()) { + return "redirect:/user/meetings"; + } + + model.addAttribute("meeting", meeting); + return "user/editMeeting"; + } + + @RequestMapping(value = "user/meetings/update/{mno}", method = RequestMethod.POST) + private String updateMeeting(@PathVariable int mno, Meeting updatedMeeting, + BindingResult result, @AuthenticationPrincipal UserDetails details, + Model model) { + if (result.hasErrors()) { + model.addAttribute("errorMessage", "Veuillez corriger les erreurs du formulaire."); + return "user/editMeeting"; + } + + Optional<Meeting> meetingOpt = mr.findById(mno); + if (!meetingOpt.isPresent()) { + return "redirect:/user/meetings"; + } + + Meeting existingMeeting = meetingOpt.get(); + User currentUser = ur.findByUsername(details.getUsername()).get(); + + if (existingMeeting.getUser().getUno() != currentUser.getUno()) { + return "redirect:/user/meetings"; + } + + existingMeeting.getRequest().setReason(updatedMeeting.getRequest().getReason()); + existingMeeting.getRequest().setDescription(updatedMeeting.getRequest().getDescription()); + + mr.save(existingMeeting); + rr.save(existingMeeting.getRequest()); + + return "redirect:/user/meetings/detail/" + mno; + } + + @RequestMapping(value = "user/meetings/delete/{mno}", method = RequestMethod.GET) + private String deleteMeeting(@PathVariable int mno, @AuthenticationPrincipal UserDetails details) { + Optional<Meeting> meetingOpt = mr.findById(mno); + if (!meetingOpt.isPresent()) { + return "redirect:/user/meetings"; + } + + Meeting meeting = meetingOpt.get(); + User currentUser = ur.findByUsername(details.getUsername()).get(); + + if (meeting.getUser().getUno() == currentUser.getUno()) { + mr.delete(meeting); + } + + return "redirect:/user/meetings"; } - @RequestMapping(value = "listeAction", method = RequestMethod.GET) - private String listeAction(){ - return "listeAction"; + private boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && + authentication.isAuthenticated() && + !(authentication instanceof AnonymousAuthenticationToken); } } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplication.java b/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplication.java index b9e07c7..502e6a9 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplication.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplication.java @@ -1,4 +1,4 @@ -package fr.but.infoetu.MeetingPlannr; +package fr.but.infoetu.meetingplannr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Meeting.java b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Meeting.java index 4c391dd..430fdb2 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Meeting.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Meeting.java @@ -1,4 +1,4 @@ -package fr.but.infoetu.MeetingPlannr.pojo; +package fr.but.infoetu.meetingplannr.pojo; import java.time.LocalDate; @@ -10,10 +10,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity +@Builder @AllArgsConstructor @Data @NoArgsConstructor @@ -25,6 +27,7 @@ public class Meeting { private int mno; @ManyToOne + @NotNull @JoinColumn(name = "rno") private Request request; diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Request.java b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Request.java index 002d7ab..e025d12 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Request.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/Request.java @@ -1,18 +1,24 @@ -package fr.but.infoetu.MeetingPlannr.pojo; +package fr.but.infoetu.meetingplannr.pojo; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.JoinColumn; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity +@Builder @AllArgsConstructor @Data @NoArgsConstructor +@Table(name = "requests") public class Request { private static final String REQUIRED = "is required"; @@ -25,4 +31,9 @@ public class Request { @NotNull(message = REQUIRED) private String description; + + @ManyToOne + @JoinColumn(name = "uno") + @NotNull(message = REQUIRED) + private User user; } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java index 9c7a610..4734efd 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java @@ -1,63 +1,98 @@ -package fr.but.infoetu.MeetingPlannr.pojo; +package fr.but.infoetu.meetingplannr.pojo; import java.time.LocalDate; -import org.springframework.boot.context.properties.bind.DefaultValue; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PastOrPresent; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.Builder.Default; +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; + @Entity +@Builder @AllArgsConstructor @Data @NoArgsConstructor @Table(name = "users") -public class User { - private static final String REQUIRED = "is required"; +public class User implements UserDetails { + private static final String REQUIRED = "est obligatoire"; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_uno_seq") + @SequenceGenerator(name = "users_uno_seq", sequenceName = "users_uno_seq", allocationSize = 1) private int uno; - @NotNull(message = REQUIRED) - @Email(message = "email not valid") - private String email; + @NotBlank(message = REQUIRED) + @Email(message = "L'email n'est pas valide") + private String username; - @NotNull(message = REQUIRED) + @NotBlank(message = REQUIRED) private String name; - @NotNull(message = REQUIRED) + @NotBlank(message = REQUIRED) private String surname; - @NotNull(message = REQUIRED) @Pattern( regexp = "^0[1-9]([-. ]?[0-9]{2}){4}$", - message = "invalid phone number" + message = "Numéro de téléphone invalide" ) private String phoneNumber; @NotNull(message = REQUIRED) - @PastOrPresent(message = "date must be within the past") + @PastOrPresent(message = "La date doit être dans le passé") private LocalDate birthdate; - @NotNull(message = REQUIRED) - @Size(min = 8, message = "password too short") + @Size(min = 8, message = "Le mot de passe est trop court") private String password; - @Column(columnDefinition = "BOOLEAN DEFAULT FALSE") - private boolean isAdmin = false; - + @Column(name = "authority", columnDefinition = "VARCHAR(50)") + private String authority = "ROLE_USER"; + + @Column(columnDefinition = "BOOLEAN DEFAULT TRUE") + private boolean enabled = true; + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority(authority)); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return enabled; + } } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/MeetingRepository.java b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/MeetingRepository.java index bc4f7cb..900111b 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/MeetingRepository.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/MeetingRepository.java @@ -1,10 +1,14 @@ -package fr.but.infoetu.MeetingPlannr.repository; +package fr.but.infoetu.meetingplannr.repository; + +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository; -import fr.but.infoetu.MeetingPlannr.pojo.Meeting; +import fr.but.infoetu.meetingplannr.pojo.Meeting; +import fr.but.infoetu.meetingplannr.pojo.User; public interface MeetingRepository extends JpaRepository<Meeting, Integer>{ + List<Meeting> findByUserOrderByDateAsc(User user); } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/RequestRepository.java b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/RequestRepository.java index 21e8ca7..bba52ce 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/RequestRepository.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/RequestRepository.java @@ -1,9 +1,9 @@ -package fr.but.infoetu.MeetingPlannr.repository; +package fr.but.infoetu.meetingplannr.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository; -import fr.but.infoetu.MeetingPlannr.pojo.Request; +import fr.but.infoetu.meetingplannr.pojo.Request; public interface RequestRepository extends JpaRepository<Request, Integer>{ diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java index 32e9bdf..628df46 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java @@ -1,12 +1,12 @@ -package fr.but.infoetu.MeetingPlannr.repository; +package fr.but.infoetu.meetingplannr.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository; -import fr.but.infoetu.MeetingPlannr.pojo.User; +import fr.but.infoetu.meetingplannr.pojo.User; public interface UserRepository extends JpaRepository<User, Integer>{ - Optional<User> findByEmail(String email); + Optional<User> findByUsername(String email); } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/security/Security.java b/src/main/java/fr/but/infoetu/MeetingPlannr/security/Security.java new file mode 100644 index 0000000..2b4046b --- /dev/null +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/security/Security.java @@ -0,0 +1,93 @@ +package fr.but.infoetu.meetingplannr.security; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +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.provisioning.JdbcUserDetailsManager; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; + +import com.zaxxer.hikari.HikariDataSource; + +import jakarta.servlet.DispatcherType; + +@Configuration +@EnableWebSecurity +public class Security { + + @Autowired + private DataSource dataSource; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public UserDetailsManager userDetailsManager() { + String usersByUsernameQuery = "select username, password, enabled from users where username = ?"; + String authoritiesByUsernameQuery = "select username, authority from users where username = ?"; + + JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource); + + users.setUsersByUsernameQuery(usersByUsernameQuery); + users.setAuthoritiesByUsernameQuery(authoritiesByUsernameQuery); + + return users; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public AuthenticationSuccessHandler authenticationSuccessHandler() { + return (request, response, authentication) -> { + System.out.println("User authenticated: " + authentication.getName()); + response.sendRedirect("/user/listeAction"); + }; + } + + @Bean + public SecurityFilterChain authorization(HttpSecurity http, HandlerMappingIntrospector intro) throws Exception { + MvcRequestMatcher.Builder mvc = new MvcRequestMatcher.Builder(intro); + return http + .csrf().disable() + .authorizeHttpRequests((authorize) -> authorize + .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll() + .requestMatchers(mvc.pattern("/public/**"),mvc.pattern("styles/**")).permitAll() + .requestMatchers(mvc.pattern("/perform_login")).permitAll() + .requestMatchers(mvc.pattern("/user/**")).authenticated() + .requestMatchers(mvc.pattern("/admin/**")).hasRole("ADMIN") + .anyRequest().authenticated() + ) + .formLogin(configurer -> + configurer.loginPage("/public/login") + .loginProcessingUrl("/public/perform_login") + .defaultSuccessUrl("/user/listeAction", true) + .failureUrl("/public/login?error") + .permitAll() + ) + .logout(configurer -> + configurer.logoutUrl("/perform_logout") + .deleteCookies("JSESSIONID") + .logoutSuccessUrl("/public/login?logout") + ) + .userDetailsService(userDetailsManager()) + .build(); + } +} diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java b/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java new file mode 100644 index 0000000..99d03f6 --- /dev/null +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java @@ -0,0 +1,22 @@ +package fr.but.infoetu.meetingplannr.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import fr.but.infoetu.meetingplannr.pojo.User; +import fr.but.infoetu.meetingplannr.repository.UserRepository; + +@Service +public class UserService { + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private UserRepository userRepository; + + public void createUser(User user) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + user.setAuthority(user.getAuthority()); + userRepository.save(user); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 553839b..aa87288 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,15 +1,27 @@ -spring.application.name=MeetingPlannr +spring.application.name=meetingplannr spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp spring.datasource.driverClassName=org.postgresql.Driver -spring.datasource.url=jdbc:postgresql://psqlserv/but3 -spring.datasource.username=paulcanceletu -spring.datasource.password=moi +spring.datasource.url=jdbc:postgresql://localhost:5432/but3 +spring.datasource.username=postgres +spring.datasource.password=admin spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true +spring.jpa.open-in-view=false -server.servlet.contextPath=/meetingplannr \ No newline at end of file +server.servlet.contextPath=/meetingplannr + +logging.level.org.hibernate.type=TRACE +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE +logging.level.org.springframework.transaction=TRACE + +logging.level.org.springframework.security=DEBUG +logging.level.org.springframework.jdbc=TRACE + +spring.security.filter.order=10 +server.servlet.session.timeout=30m \ No newline at end of file diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 7f7073e..d66ef5a 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,7 +1,37 @@ -INSERT INTO users (email, name, surname, phone_number, birthdate, password) VALUES ('john.doe@example.com', 'John', 'Doe', '0612345678', '1990-05-15', 'password123'); -INSERT INTO users (email, name, surname, phone_number, birthdate, password) VALUES ('jane.smith@example.com', 'Jane', 'Smith', '0698765432', '1985-08-22', 'securepwd'); -INSERT INTO users (email, name, surname, phone_number, birthdate, password) VALUES ('robert.brown@example.com', 'Robert', 'Brown', '0711121314', '1992-03-12', 'robert2024'); -INSERT INTO users (email, name, surname, phone_number, birthdate, password) VALUES ('emily.jones@example.com', 'Emily', 'Jones', '0645678901', '2000-11-09', 'emily!secure'); -INSERT INTO users (email, name, surname, phone_number, birthdate, password) VALUES ('william.davis@example.com', 'William', 'Davis', '0623456789', '1988-07-30', 'password789'); -INSERT INTO users (email, name, surname, phone_number, birthdate, password, is_admin) VALUES ('paul.cancel@net.fr', 'Paul', 'Cancel', '0682015653', '2004-12-26', 'maywennjuliagoat', true); -INSERT INTO users (email, name, surname, phone_number, birthdate, password, is_admin) VALUES ('theo.vienne@net.fr', 'Theo', 'Vienne', '0619515793', '2000-08-31', 'noemiegoat', true); +DROP SEQUENCE IF EXISTS users_uno_seq; +CREATE SEQUENCE users_uno_seq START WITH 1; + +-- password: password123 +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'john.doe@example.com', 'John', 'Doe', '0612345678', '1990-05-15', '$2a$12$o0C1lgpgzoxPrE64DHda6O0DEDqQznVxqXb5y6gzWne3BP4nZMWrC', 'ROLE_USER', true); + +-- password: securepwd +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'jane.smith@example.com', 'Jane', 'Smith', '0698765432', '1985-08-22', '$2a$12$9DmwZZv31epkRx6kUOCeueYRCrlAUyV1J0iB6eienUWRKT3ozpLGu', 'ROLE_USER', true); + +-- password: robert2024 +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'robert.brown@example.com', 'Robert', 'Brown', '0711121314', '1992-03-12', '$2a$12$bTyqZfn.UGxEXThkvSeU2uKUgO1o6rRadIqg8yYeo9WFLIcnVgGpq', 'ROLE_USER', true); + +-- password: emily!secure +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'emily.jones@example.com', 'Emily', 'Jones', '0645678901', '2000-11-09', '$2a$12$MDEm7.8TV90OrE1PhuDfCeJlrLlZAtIj0H5uNEybKVO.cGru3qUD6', 'ROLE_USER', true); + +-- password: password789 +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'william.davis@example.com', 'William', 'Davis', '0623456789', '1988-07-30', '$2a$12$/T572UEV.ptFg0.KEeSLnev8tj75Ubxm1xAsHZx2/kyneBdDTGbuu', 'ROLE_USER', true); + +-- password: testgoat +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'paul.cancel@net.fr', 'Paul', 'Cancel', '0682015653', '2004-12-26', '$2a$12$4agvRLzb.KKGSKLjDi4bZelLWCAQKI15xwFMEI4mpYYgfeo4DyThW', 'ROLE_ADMIN', true); + +-- password: noemiegoat +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'theo.vienne@net.fr', 'Theo', 'Vienne', '0619515793', '2000-08-31', '$2a$12$XWqr1MGt9oSxRm2YfEMQle4QWq9r9QcpSXDcOgsEzEQTqtLRdBgcC', 'ROLE_ADMIN', true); + + +INSERT INTO requests (reason, description, uno) VALUES ('Rendez-vous pour discussion projet', 'Discussion des objectifs du projet', 6); +INSERT INTO requests (reason, description, uno) VALUES ('Point d''avancement mensuel', 'Revue mensuelle des progrès', 6); +INSERT INTO requests (reason, description, uno) VALUES ('Réunion de planification', 'Planification des prochaines étapes', 6); +INSERT INTO requests (reason, description, uno) VALUES ('Bilan trimestriel', 'Evaluation des résultats du trimestre', 6); +INSERT INTO requests (reason, description, uno) VALUES ('Session de développement', 'Session de codage en équipe', 6); + +-- Création des meetings liés aux requests +INSERT INTO meeting (date, uno, rno) VALUES ('2024-03-20', 6, (SELECT rno FROM requests WHERE reason = 'Rendez-vous pour discussion projet')); +INSERT INTO meeting (date, uno, rno) VALUES ('2024-03-25', 6, (SELECT rno FROM requests WHERE reason = 'Point d''avancement mensuel')); +INSERT INTO meeting (date, uno, rno) VALUES ('2024-04-01', 6, (SELECT rno FROM requests WHERE reason = 'Réunion de planification')); +INSERT INTO meeting (date, uno, rno) VALUES ('2024-04-15', 6, (SELECT rno FROM requests WHERE reason = 'Bilan trimestriel')); +INSERT INTO meeting (date, uno, rno) VALUES ('2024-05-02', 6, (SELECT rno FROM requests WHERE reason = 'Session de développement')); \ No newline at end of file diff --git a/src/main/resources/static/styles/main.css b/src/main/resources/static/styles/main.css new file mode 100644 index 0000000..947458f --- /dev/null +++ b/src/main/resources/static/styles/main.css @@ -0,0 +1,361 @@ +/* Style de base */ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f9; +} +h1, h2 { + text-align: center; + color: #333; +} +.container { + max-width: 800px; + margin: 90px auto 80px auto; /* Réduit la marge du haut */ + padding: 0 30px 30px 30px; /* Supprime le padding du haut */ + background-color: #fff; + border-radius: 15px; + box-shadow: 0 5px 15px rgba(0,0,0,0.08); + min-height: calc(100vh - 140px); /* Ajout d'une hauteur minimale */ + display: flex; + flex-direction: column; +} +.error { + color: red; + font-size: 0.9em; +} + +/* Boutons */ +button { + padding: 12px 24px; + font-size: 16px; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} +button.logout { + background-color: #dc3545; +} +button.logout:hover { + background-color: #c82333; +} +button.submit { + background: #28a745; +} +button.submit:hover { + background-color: #218838; +} +button.back { + background-color: #007bff; +} +button.back:hover { + background-color: #0056b3; +} + + +/* Formulaires */ +.form-container, .container { + max-width: 500px; + margin: 50px auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; +} +.form-group { + margin-bottom: 15px; +} +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #555; +} +.form-group input { + width: 100%; + padding: 12px; + box-sizing: border-box; + border: 2px solid #e1e1e1; + border-radius: 8px; + font-size: 16px; + transition: border-color 0.3s ease; +} +.form-group input:focus, .form-group textarea:focus { + border-color: #007bff; + outline: none; + box-shadow: 0 0 0 3px rgba(0,123,255,0.1); +} + +/* Style pour le formulaire d'édition */ +.form-group textarea { + width: 100%; + padding: 12px; + box-sizing: border-box; + border: 2px solid #e1e1e1; + border-radius: 8px; + font-size: 16px; + resize: vertical; + min-height: 100px; + transition: border-color 0.3s ease; +} + +.form-group input[type="date"] { + width: 100%; + padding: 8px; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 14px; +} + +/* Profil */ +.profile-info { + background: #f8f9fa; + padding: 25px; + border-radius: 10px; + margin-top: 30px; +} +.profile-info p { + margin: 15px 0; + padding: 10px; + background: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} +.actions { + margin-top: 30px; + text-align: center; +} +.actions button { + margin: 10px 0; +} + +/* Bannière */ +.banner { + background: linear-gradient(135deg, #0056b3, #007bff); + color: white; + padding: 20px; + text-align: center; + font-size: 18px; + margin: 50px -30px 30px -30px; + border-radius: 15px 15px 15px 15px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + position: relative; +} + +.meeting-detail { + padding: 20px; + margin-bottom: 30px; +} + +.detail-section { + background-color: #f8f9fa; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; +} + +.detail-section h2 { + color: #007bff; + font-size: 1.2em; + margin-bottom: 15px; + text-align: left; +} + +.detail-section p { + margin: 10px 0; + line-height: 1.5; +} + +.detail-section strong { + color: #495057; + margin-right: 10px; +} + +.actions { + display: flex; + gap: 10px; + justify-content: center; +} + +/* Nouveau conteneur pour le contenu principal */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; +} + +.liste-actions { + display: flex; + flex-direction: column; + gap: 15px; + justify-content: center; + align-items: center; + padding: 20px 0; +} + +.liste-actions form { + width: 250px; +} + +.liste-actions button { + width: 100%; +} + +.meeting-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + background: white; + border-radius: 10px; + margin-bottom: 20px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.meeting-item:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0,0,0,0.15); +} + +.meeting-item button { + margin-left: 10px; +} + +.meeting-content { + display: flex; + flex-direction: column; + gap: 10px; +} + +.meeting-info { + flex-grow: 1; +} + +.meeting-info p { + margin: 5px 0; + color: #333; +} + +.meeting-actions { + display: flex; + justify-content: flex-end; +} + +.meeting-actions button { + width: 100%; +} + +.meeting-info { + flex-grow: 1; +} + +.meeting-info p { + margin: 5px 0; +} + +.meeting-actions { + margin-left: 20px; + flex-shrink: 0; +} + +/* Header Styles - Updated */ +.app-header { + background: linear-gradient(135deg, #0056b3, #007bff); + color: white; + padding: 1rem; /* Réduit légèrement le padding */ + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.header-content { + max-width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 10px; +} + +.logo a { + text-decoration: none; + color: white; + display: flex; + align-items: center; + gap: 15px; +} + +.logo-icon { + font-size: 2rem; +} + +.logo h1 { + margin: 0; + font-size: 2.2rem; + font-weight: 700; + letter-spacing: 1px; +} + +.header-subtitle { + font-size: 1.1rem; + margin-top: 0.5rem; + opacity: 0.9; +} + +/* Footer - Updated */ +.app-footer { + background: linear-gradient(135deg, #333, #222); + color: white; + padding: 1.5rem; + position: fixed; + bottom: 0; + left: 0; + right: 0; + text-align: center; + box-shadow: 0 -2px 10px rgba(0,0,0,0.1); +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + font-size: 0.9rem; + opacity: 0.9; +} + +/* Cards Style */ +.meeting-item { + background: white; + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.meeting-item:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0,0,0,0.15); +} + +/* ...existing code... */ diff --git a/src/main/webapp/WEB-INF/jsp/common/footer.jsp b/src/main/webapp/WEB-INF/jsp/common/footer.jsp new file mode 100644 index 0000000..9642d63 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/common/footer.jsp @@ -0,0 +1,6 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<footer class="app-footer"> + <div class="footer-content"> + <p>© 2024 MeetingPlannr - Tous droits réservés</p> + </div> +</footer> diff --git a/src/main/webapp/WEB-INF/jsp/common/header.jsp b/src/main/webapp/WEB-INF/jsp/common/header.jsp new file mode 100644 index 0000000..246a409 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/common/header.jsp @@ -0,0 +1,13 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<header class="app-header"> + <div class="header-content"> + <div class="logo"> + <a href="${pageContext.request.contextPath}/user/listeAction"> + <h1>MeetingPlannr</h1> + </a> + </div> + <div class="header-subtitle"> + Gestionnaire de rendez-vous + </div> + </div> +</header> diff --git a/src/main/webapp/WEB-INF/jsp/listeAction.jsp b/src/main/webapp/WEB-INF/jsp/listeAction.jsp deleted file mode 100644 index 325f434..0000000 --- a/src/main/webapp/WEB-INF/jsp/listeAction.jsp +++ /dev/null @@ -1,81 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ page import="fr.but.infoetu.MeetingPlannr.pojo.User" %> -<!DOCTYPE html> -<html> -<head> - <title>Liste des Actions</title> - <style> - body { - font-family: Arial, sans-serif; - margin: 0; - padding: 0; - background-color: #f4f4f9; - } - .container { - max-width: 800px; - margin: 50px auto; - padding: 20px; - background-color: #fff; - border-radius: 8px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - } - h1 { - text-align: center; - color: #333; - } - .actions { - display: flex; - flex-direction: column; - gap: 15px; - } - .actions button { - padding: 10px 20px; - font-size: 16px; - color: #fff; - background-color: #007bff; - border: none; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s; - } - .actions button:hover { - background-color: #0056b3; - } - .actions button.logout { - background-color: #dc3545; - } - .actions button.logout:hover { - background-color: #c82333; - } - </style> -</head> -<body> - <% - User currentUser = (User) request.getSession().getAttribute("currentUser") - %> - <div class="container"> - <h1>Actions Disponibles</h1> - <div class="actions"> - <!-- Bouton pour voir le calendrier et poser des requêtes --> - <form action="/calendar" method="get"> - <button type="submit">Voir le Calendrier</button> - </form> - - <!-- Bouton pour accéder à ses rendez-vous --> - <form action="/appointments" method="get"> - <button type="submit">Mes Rendez-Vous</button> - </form> - - <!-- Bouton pour voir les détails de son profil --> - <form action="/profile" method="get"> - <button type="submit">Mon Profil</button> - </form> - - <!-- Bouton pour se déconnecter --> - <form action="/logout" method="post"> - <button type="submit" class="logout">Se Déconnecter</button> - </form> - </div> - </div> -</body> -</html> diff --git a/src/main/webapp/WEB-INF/jsp/login.jsp b/src/main/webapp/WEB-INF/jsp/login.jsp deleted file mode 100644 index bf259c3..0000000 --- a/src/main/webapp/WEB-INF/jsp/login.jsp +++ /dev/null @@ -1,88 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> -<!DOCTYPE html> -<html> -<head> - <title>Login</title> - <style> - body { - font-family: Arial, sans-serif; - margin: 0; - padding: 0; - background-color: #f4f4f9; - } - .container { - max-width: 400px; - margin: 100px auto; - padding: 20px; - background: #ffffff; - border: 1px solid #ddd; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - } - h2 { - text-align: center; - color: #333; - } - .form-group { - margin-bottom: 15px; - } - label { - display: block; - margin-bottom: 5px; - color: #555; - } - input[type="email"], - input[type="password"] { - width: 100%; - padding: 10px; - border: 1px solid #ccc; - border-radius: 5px; - font-size: 14px; - } - button { - width: 100%; - padding: 10px; - background: #007bff; - border: none; - color: white; - font-size: 16px; - border-radius: 5px; - cursor: pointer; - margin: 10px; - } - button:hover { - background: #0056b3; - } - .error { - color: red; - font-size: 14px; - } - </style> -</head> -<body> - <div class="container"> - <h2>Login</h2> - <form action="login" method="POST"> - <div class="form-group"> - <label for="email">Email :</label> - <input type="email" id="email" name="email" placeholder="Entrez votre email"> - </div> - <div class="form-group"> - <label for="password">Mot de passe :</label> - <input type="password" id="password" name="password" placeholder="Entrez votre mot de passe"> - </div> - <button type="submit">Se connecter</button> - <a href="/register"> - Créer un nouveau compte - </a> - </form> - <% - String errorMessage = (String) request.getAttribute("errorMessage"); - if (errorMessage != null) { - %> - <div class="error"><%= errorMessage %></div> - <% - } - %> - </div> -</body> -</html> diff --git a/src/main/webapp/WEB-INF/jsp/public/login.jsp b/src/main/webapp/WEB-INF/jsp/public/login.jsp new file mode 100644 index 0000000..0b5499d --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/public/login.jsp @@ -0,0 +1,45 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<!DOCTYPE html> +<html> +<head> + <title>Login</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <div class="container"> + <h2>Login</h2> + <form action="${pageContext.request.contextPath}/public/perform_login" method="POST"> + <div class="form-group"> + <label for="username">Email :</label> + <input type="email" id="username" name="username" placeholder="Entrez votre email"> + </div> + <div class="form-group"> + <label for="password">Mot de passe :</label> + <input type="password" id="password" name="password" placeholder="Entrez votre mot de passe"> + </div> + <button type="submit">Se connecter</button> + <a href="register"> + Créer un nouveau compte + </a> + </form> + <% + String errorMessage = (String) request.getAttribute("errorMessage"); + if (errorMessage != null) { + %> + <div class="error"><%= errorMessage %></div> + <% + } + %> + <br> + <% + String error = request.getParameter("error"); + if (error != null) { + %> + <div class="error">Email ou mot de passe incorrect</div> + <% + } + %> + </div> + +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/register.jsp b/src/main/webapp/WEB-INF/jsp/public/register.jsp similarity index 54% rename from src/main/webapp/WEB-INF/jsp/register.jsp rename to src/main/webapp/WEB-INF/jsp/public/register.jsp index 9c697cc..33d5488 100644 --- a/src/main/webapp/WEB-INF/jsp/register.jsp +++ b/src/main/webapp/WEB-INF/jsp/public/register.jsp @@ -3,57 +3,15 @@ <html> <head> <title>Register</title> - <style> - body { - font-family: Arial, sans-serif; - } - .form-container { - max-width: 500px; - margin: 50px auto; - padding: 20px; - border: 1px solid #ccc; - border-radius: 5px; - } - .form-container h1 { - text-align: center; - } - .form-group { - margin-bottom: 15px; - } - .form-group label { - display: block; - font-weight: bold; - } - .form-group input { - width: 100%; - padding: 8px; - box-sizing: border-box; - } - .error { - color: red; - font-size: 0.9em; - } - .form-group button { - width: 100%; - padding: 10px; - background-color: #28a745; - color: white; - border: none; - border-radius: 5px; - font-size: 1em; - } - .form-group button:hover { - background-color: #218838; - } - </style> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> </head> <body> <div class="form-container"> <h1>Register</h1> <form action="register" method="post"> <div class="form-group"> - <label for="email">Email:</label> - <input type="email" id="email" name="email"> + <label for="username">Email:</label> + <input type="email" id="username" name="username"> </div> <div class="form-group"> <label for="name">Name:</label> @@ -77,8 +35,19 @@ </div> <div class="form-group"> <button type="submit">Register</button> + <a href="login"> + Se connecter + </a> </div> </form> + <% + String errorMessage = (String) request.getAttribute("errorMessage"); + if (errorMessage != null) { + %> + <div class="error"><%= errorMessage %></div> + <% + } + %> </div> </body> </html> diff --git a/src/main/webapp/WEB-INF/jsp/user/detailRendezVous.jsp b/src/main/webapp/WEB-INF/jsp/user/detailRendezVous.jsp new file mode 100644 index 0000000..b8c9e1a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/detailRendezVous.jsp @@ -0,0 +1,31 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Détail du rendez-vous</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <div class="container"> + <div class="banner"> + <h1>Détail du rendez-vous</h1> + </div> + + <div class="meeting-detail"> + <div class="detail-section"> + <h2>Informations générales</h2> + <p><strong>Motif:</strong> ${meeting.getRequest().getReason()}</p> + <p><strong>Description:</strong> ${meeting.getRequest().getDescription()}</p> + <p><strong>Date:</strong> ${meeting.date}</p> + </div> + </div> + + <div class="actions"> + <button onclick="window.location.href='${pageContext.request.contextPath}/user/meetings'" class="back">Retour à la liste</button> + <button class="submit" onclick="window.location.href='${pageContext.request.contextPath}/user/meetings/edit/${meeting.mno}'">Modifier</button> + <button class="logout" onclick="if(confirm('Êtes-vous sûr de vouloir supprimer ce rendez-vous?')) window.location.href='${pageContext.request.contextPath}/user/meetings/delete/${meeting.mno}'">Supprimer</button> + </div> + </div> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/user/editMeeting.jsp b/src/main/webapp/WEB-INF/jsp/user/editMeeting.jsp new file mode 100644 index 0000000..7e2c819 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/editMeeting.jsp @@ -0,0 +1,45 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Modifier le rendez-vous</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <div class="container"> + <div class="banner"> + <h1>Modifier le rendez-vous</h1> + </div> + + <form:form action="${pageContext.request.contextPath}/user/meetings/update/${meeting.mno}" + method="POST" + modelAttribute="meeting" + class="form-container"> + + <div class="form-group"> + <label for="request.reason">Motif :</label> + <form:input path="request.reason" required="true" /> + <form:errors path="request.reason" cssClass="error" /> + </div> + + <div class="form-group"> + <label for="request.description">Description :</label> + <form:textarea path="request.description" required="true" rows="4" /> + <form:errors path="request.description" cssClass="error" /> + </div> + + + <div class="actions"> + <button type="submit" class="submit">Enregistrer</button> + <button type="button" onclick="window.location.href='${pageContext.request.contextPath}/user/meetings/detail/${meeting.mno}'" class="logout">Annuler</button> + </div> + + <div class="error"> + ${errorMessage} + </div> + </form:form> + </div> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/user/editProfile.jsp b/src/main/webapp/WEB-INF/jsp/user/editProfile.jsp new file mode 100644 index 0000000..353581a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/editProfile.jsp @@ -0,0 +1,40 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.User" %> +<!DOCTYPE html> +<html lang="fr"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Modifier le Profil</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <div class="container"> + <h1>Modifier le Profil</h1> + <div class="form-container"> + <form action="${pageContext.request.contextPath}/user/updateProfile" method="post"> + <div class="form-group"> + <label for="name">Prénom :</label> + <input type="text" id="name" name="name" value="${currentUser.getName()}" required> + </div> + <div class="form-group"> + <label for="surname">Nom :</label> + <input type="text" id="surname" name="surname" value="${currentUser.getSurname()}" required> + </div> + <div class="actions"> + <button type="submit" class="submit">Enregistrer les modifications</button> + <button type="button" class="logout" onclick="window.location.href='${pageContext.request.contextPath}/user/profile'">Annuler</button> + </div> + </form> + <% + String errorMessage = (String) request.getAttribute("errorMessage"); + if (errorMessage != null) { + %> + <div class="error"><%= errorMessage %></div> + <% + } + %> + </div> + </div> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/user/listeAction.jsp b/src/main/webapp/WEB-INF/jsp/user/listeAction.jsp new file mode 100644 index 0000000..5c5aba4 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/listeAction.jsp @@ -0,0 +1,38 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.User" %> +<!DOCTYPE html> +<html> +<head> + <title>Liste des Actions - MeetingPlannr</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <%@ include file="../common/header.jsp" %> + <div class="container"> + <div class="banner"> + Bonjour, ${currentUser.name} ${currentUser.surname} (${currentUser.username}) + </div> + <div class="main-content"> + <h1>Actions Disponibles</h1> + <div class="liste-actions"> + <form action="${pageContext.request.contextPath}/user/calendar" method="get"> + <button type="submit" class="submit">Voir le Calendrier</button> + </form> + + <form action="${pageContext.request.contextPath}/user/meetings" method="get"> + <button type="submit" class="submit">Mes Rendez-Vous</button> + </form> + + <form action="${pageContext.request.contextPath}/user/profile" method="get"> + <button type="submit" class="submit">Mon Profil</button> + </form> + + <form action="${pageContext.request.contextPath}/perform_logout" method="post"> + <button type="submit" class="logout">Se Déconnecter</button> + </form> + </div> + </div> + </div> + <%@ include file="../common/footer.jsp" %> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/user/profile.jsp b/src/main/webapp/WEB-INF/jsp/user/profile.jsp new file mode 100644 index 0000000..8b7dd22 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/profile.jsp @@ -0,0 +1,29 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.User" %> +<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> +<!DOCTYPE html> +<html> +<head> + <title>Mon Profil</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <%@ include file="../common/header.jsp" %> + <div class="container"> + <h1>Mon Profil</h1> + <div class="profile-info"> + <p><strong>Nom :</strong> ${currentUser.surname}</p> + <p><strong>Prénom :</strong> ${currentUser.name}</p> + <p><strong>Email :</strong> ${currentUser.username}</p> + </div> + <div class="actions"> + <form action="${pageContext.request.contextPath}/user/editProfile" method="get"> + <button type="submit" class="submit">Modifier mon Profil</button> + </form> + <form action="${pageContext.request.contextPath}/user/listeAction" method="get"> + <button type="submit" class="back">Retour aux Actions</button> + </form> + </div> + </div> +</body> +</html> \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/user/rendezVous.jsp b/src/main/webapp/WEB-INF/jsp/user/rendezVous.jsp new file mode 100644 index 0000000..e43f5f5 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/user/rendezVous.jsp @@ -0,0 +1,46 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.User" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.Meeting" %> +<%@ page import="java.util.List" %> +<%@ page import="java.time.format.DateTimeFormatter" %> +<!DOCTYPE html> +<html> +<head> + <title>Mes Rendez-vous</title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <div class="container"> + <h1>Mes Rendez-vous</h1> + <div class="meetings-list"> + <% + List<Meeting> meetings = (List<Meeting>) request.getAttribute("meetings"); + if (meetings != null && !meetings.isEmpty()) { + for (Meeting meeting : meetings) { + %> + <div class="meeting-item"> + <div class="meeting-content"> + <div class="meeting-info"> + <p>Date: <%= meeting.getDate().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) %></p> + <p>Motif: <%= meeting.getRequest().getReason() %></p> + </div> + <div class="meeting-actions"> + <button onclick="window.location.href='${pageContext.request.contextPath}/user/meetings/detail/<%= meeting.getMno() %>'" class="submit">Voir détails</button> + </div> + </div> + </div> + <% + } + } else { + %> + <p>Aucun rendez-vous programmé.</p> + <% + } + %> + </div> + <div class="actions"> + <button onclick="window.location.href='${pageContext.request.contextPath}/user/listeAction'" class="back">Retour</button> + </div> + </div> +</body> +</html> diff --git a/src/test/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplicationTests.java b/src/test/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplicationTests.java index 37ced39..ee1f890 100644 --- a/src/test/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplicationTests.java +++ b/src/test/java/fr/but/infoetu/MeetingPlannr/MeetingPlannrApplicationTests.java @@ -1,4 +1,4 @@ -package fr.but.infoetu.MeetingPlannr; +package fr.but.infoetu.meetingplannr; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -- GitLab