merge conflict
@@ -31,6 +31,10 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||
implementation 'org.mariadb.jdbc:mariadb-java-client:2.2.0'
|
||||
//Mail
|
||||
implementation 'org.springframework.boot:spring-boot-starter-mail'
|
||||
implementation 'jakarta.mail:jakarta.mail-api'
|
||||
implementation 'jakarta.activation:jakarta.activation-api'
|
||||
//Securité
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||
@@ -40,6 +44,8 @@ dependencies {
|
||||
//test
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
//data
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
||||
@@ -2,6 +2,8 @@ package fr.eni.enchere.bll;
|
||||
|
||||
import fr.eni.enchere.bo.Article;
|
||||
import fr.eni.enchere.bo.SearchArticleCritere;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -13,5 +15,5 @@ public interface ArticleService {
|
||||
void deleteArticle(int id);
|
||||
void updateArticle(int id);
|
||||
List<Article> findArticleByTitle(String title);
|
||||
List<Article> searchArticle(SearchArticleCritere critere);
|
||||
Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package fr.eni.enchere.bll;
|
||||
import fr.eni.enchere.bo.Article;
|
||||
import fr.eni.enchere.bo.SearchArticleCritere;
|
||||
import fr.eni.enchere.dal.ArticleRepository;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -45,8 +47,9 @@ public class ArticleServiceImpl implements ArticleService{
|
||||
return articleRepository.findArticleByTitle(title);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Article> searchArticle(SearchArticleCritere critere) {
|
||||
return articleRepository.searchArticle(critere);
|
||||
public Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable) {
|
||||
return articleRepository.searchArticlePageable(critere, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
12
src/main/java/fr/eni/enchere/bll/ForgotPasswordService.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package fr.eni.enchere.bll;
|
||||
|
||||
import fr.eni.enchere.bo.ForgotPassword;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ForgotPasswordService {
|
||||
|
||||
ForgotPassword getForgotPassword(String link);
|
||||
void setForgotPassword(String email);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.eni.enchere.bll;
|
||||
|
||||
import fr.eni.enchere.bo.ForgotPassword;
|
||||
import fr.eni.enchere.dal.ForgotPasswordRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service("ForgotPasswordService")
|
||||
public class ForgotPasswordServiceImpl implements ForgotPasswordService {
|
||||
|
||||
private ForgotPasswordRepository forgotPasswordRepository;
|
||||
|
||||
public ForgotPasswordServiceImpl(ForgotPasswordRepository forgotPasswordRepository) {
|
||||
this.forgotPasswordRepository = forgotPasswordRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgotPassword getForgotPassword(String link) {
|
||||
return forgotPasswordRepository.getForgotPasswordByLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForgotPassword(String email) {
|
||||
forgotPasswordRepository.setForgotPasswords(email);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package fr.eni.enchere.bll;
|
||||
|
||||
import fr.eni.enchere.bo.UserProfil;
|
||||
import org.apache.catalina.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -8,8 +9,10 @@ public interface UserService {
|
||||
List<UserProfil> listeUtilisateurs();
|
||||
UserProfil utilisateurById(int id);
|
||||
UserProfil utilisateurByName(String username);
|
||||
UserProfil utilisateurByEmail(String email);
|
||||
List<String> listPseudo();
|
||||
List<String> listEmail();
|
||||
String getUserByMail(String mail);
|
||||
void setUtilisateur(UserProfil utilisateur);
|
||||
void setCredit(float credit, int id);
|
||||
void deleteUtilisateur(int id);
|
||||
|
||||
@@ -30,6 +30,11 @@ public class UserServiceImpl implements UserService {
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfil utilisateurByEmail(String email) {
|
||||
return userRepository.findUserByEmail(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listPseudo() {
|
||||
return userRepository.findAllUsernames();
|
||||
@@ -40,6 +45,11 @@ public class UserServiceImpl implements UserService {
|
||||
return userRepository.findAllEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserByMail(String mail) {
|
||||
return userRepository.findByEmail(mail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUtilisateur(UserProfil utilisateur) {
|
||||
userRepository.save(utilisateur);
|
||||
|
||||
64
src/main/java/fr/eni/enchere/bo/ForgotPassword.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package fr.eni.enchere.bo;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class ForgotPassword {
|
||||
|
||||
private int id;
|
||||
private String email;
|
||||
private String link;
|
||||
private Calendar dateCreate;
|
||||
private Calendar dateExpire;
|
||||
|
||||
public ForgotPassword() {}
|
||||
|
||||
public ForgotPassword(int id, String email, String link, Calendar dateCreate, Calendar dateExpire) {
|
||||
setId(id);
|
||||
setEmail(email);
|
||||
setLink(link);
|
||||
setDateCreate(dateCreate);
|
||||
setDateExpire(dateExpire);
|
||||
}
|
||||
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
public void setLink(String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Calendar getDateCreate() {
|
||||
return dateCreate;
|
||||
}
|
||||
|
||||
public void setDateCreate(Calendar dateCreate) {
|
||||
this.dateCreate = dateCreate;
|
||||
}
|
||||
|
||||
public Calendar getDateExpire() {
|
||||
return dateExpire;
|
||||
}
|
||||
|
||||
public void setDateExpire(Calendar dateExpire) {
|
||||
this.dateExpire = dateExpire;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,14 +4,9 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.web.context.request.RequestContextListener;
|
||||
import org.springframework.web.filter.RequestContextFilter;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -44,7 +46,7 @@ public class AccueilController {
|
||||
}
|
||||
|
||||
@GetMapping({"/", "/accueil"})
|
||||
public String viewAccueil(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) {
|
||||
public String viewAccueil(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "6") int size, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) {
|
||||
model.addAttribute("categories", categorieService.findAllCategories());
|
||||
model.addAttribute("requestURI", request.getRequestURI());
|
||||
SearchArticleCritere critere = new SearchArticleCritere();
|
||||
@@ -55,15 +57,21 @@ public class AccueilController {
|
||||
}
|
||||
critere.setVenteOptions(venteOptions);
|
||||
critere.setAchatOptions(achatOptions);
|
||||
model.addAttribute("articles", articleService.searchArticle(critere));
|
||||
|
||||
// Pagination
|
||||
Page<Article> articlePage = articleService.searchArticlePageable(critere, PageRequest.of(page, size));
|
||||
model.addAttribute("articles", articlePage.getContent());
|
||||
model.addAttribute("currentPage", page);
|
||||
model.addAttribute("totalPages", articlePage.getTotalPages());
|
||||
|
||||
return "accueil";
|
||||
}
|
||||
|
||||
@PostMapping("/accueil")
|
||||
public String handleSearch(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam("searchTitle") String searchTitle, @RequestParam(value = "searchCategory", required = false) Integer searchCategory, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions ) {
|
||||
public String handleSearch(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "6") int size, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) {
|
||||
|
||||
|
||||
return viewAccueil(request, userDetails, searchTitle, searchCategory, model, venteOptions, achatOptions);
|
||||
return viewAccueil(request, userDetails, searchTitle, searchCategory, page, size, model, venteOptions, achatOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -68,6 +68,12 @@ public class ArticleController {
|
||||
Optional<Float> maxMontantEnchere = lastEnchere.stream()
|
||||
.map(Enchere::getMontantEnchere) // Récupère seulement les montants d'enchère
|
||||
.max(Float::compareTo);
|
||||
UserProfil currentUser = userService.utilisateurByName(authentication.getName());
|
||||
boolean isArticleCurrentUser = false;
|
||||
if (currentUser.getId() == user.getId()) {
|
||||
isArticleCurrentUser = true;
|
||||
}
|
||||
model.addAttribute("isArticleCurrentUser", isArticleCurrentUser);
|
||||
model.addAttribute("article", article);
|
||||
model.addAttribute("username", user);
|
||||
model.addAttribute("cate", cate.getLibelle());
|
||||
|
||||
44
src/main/java/fr/eni/enchere/controllers/BankController.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package fr.eni.enchere.controllers;
|
||||
|
||||
import fr.eni.enchere.bll.ArticleService;
|
||||
import fr.eni.enchere.bll.UserService;
|
||||
import fr.eni.enchere.bo.UserProfil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller()
|
||||
@RequestMapping("/bank")
|
||||
public class BankController {
|
||||
|
||||
@Autowired
|
||||
private final UserService userService;
|
||||
|
||||
public BankController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("/home")
|
||||
public String homeCredit(Model model) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!authentication.getName().equals("anonymousUser")){
|
||||
return "redirect:/accueil";
|
||||
}
|
||||
String username = authentication.getName();
|
||||
UserProfil userProfile = userService.utilisateurByName(username);
|
||||
model.addAttribute("userProfile", userProfile);
|
||||
return "bank";
|
||||
}
|
||||
|
||||
@PostMapping("/checkout")
|
||||
public String addCreditCheckout() {
|
||||
|
||||
return "bank";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package fr.eni.enchere.controllers;
|
||||
|
||||
import fr.eni.enchere.bll.ForgotPasswordService;
|
||||
import fr.eni.enchere.bll.UserService;
|
||||
import fr.eni.enchere.bo.ForgotPassword;
|
||||
import fr.eni.enchere.bo.UserProfil;
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Controller()
|
||||
@RequestMapping("/forgotPassword")
|
||||
public class ForgotPasswordController {
|
||||
|
||||
@Autowired
|
||||
private final UserService userService;
|
||||
private EmailValidator emailValidator;
|
||||
private ForgotPasswordService forgotPasswordService;
|
||||
|
||||
public ForgotPasswordController(EmailValidator emailValidator, ForgotPasswordService forgotPasswordService, UserService userService) {
|
||||
this.userService = userService;
|
||||
this.emailValidator = emailValidator;
|
||||
this.forgotPasswordService = forgotPasswordService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String forgotPassword(Model model, @RequestParam(value = "link", required = false) String link) {
|
||||
// Vérifier si l'utilisateur est déjà authentifié
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!authentication.getName().equals("anonymousUser")){
|
||||
return "redirect:/accueil";
|
||||
}
|
||||
if (link != null) {
|
||||
ForgotPassword forgotPassword = forgotPasswordService.getForgotPassword(link);
|
||||
if (forgotPassword != null) {
|
||||
Calendar dateNow = Calendar.getInstance();
|
||||
if (dateNow.before(forgotPassword.getDateExpire())){
|
||||
return "redirect:/forgotPassword/changePassword?link=" + forgotPassword.getLink();
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePasswordExpired?link=" + forgotPassword.getLink();
|
||||
}
|
||||
}else{
|
||||
return "redirect:/security/forgotPassword";
|
||||
}
|
||||
}else{
|
||||
return "security/forgotPassword";
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String createLinkForgotPassword(@RequestParam("email") String email) {
|
||||
if (email.isEmpty()){
|
||||
return "redirect:/forgotPassword?error";
|
||||
}else{
|
||||
if (!emailValidator.isValid(email)) {
|
||||
return "redirect:/forgotPassword?error";
|
||||
}
|
||||
}
|
||||
//Vérification de l'email dans la base de donnée
|
||||
if (userService.getUserByMail(email) == null){
|
||||
return "redirect:/forgotPassword?error";
|
||||
}
|
||||
forgotPasswordService.setForgotPassword(email);
|
||||
return "redirect:/forgotPassword?mailSend";
|
||||
}
|
||||
|
||||
@GetMapping("/changePassword")
|
||||
public String forgotPasswordChangePassword(Model model, @RequestParam(value = "link", required = true) String link) {
|
||||
// Vérifier si l'utilisateur est déjà authentifié
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!authentication.getName().equals("anonymousUser")){
|
||||
return "redirect:/accueil";
|
||||
}
|
||||
ForgotPassword forgotPassword = forgotPasswordService.getForgotPassword(link);
|
||||
if (forgotPassword != null) {
|
||||
Calendar dateNow = Calendar.getInstance();
|
||||
if (dateNow.before(forgotPassword.getDateExpire())){
|
||||
return "security/changePassword";
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePasswordExpired?link=" + forgotPassword.getLink();
|
||||
}
|
||||
}else{
|
||||
return "security/forgotPassword";
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/changePassword/check")
|
||||
public String changePassword(@RequestParam("email") String email,
|
||||
@RequestParam("password") String password,
|
||||
@RequestParam("confirmPassword") String confirmPassword,
|
||||
@RequestParam("link") String link) {
|
||||
ForgotPassword forgotPassword = forgotPasswordService.getForgotPassword(link);
|
||||
if (forgotPassword != null) {
|
||||
Calendar dateNow = Calendar.getInstance();
|
||||
if (dateNow.before(forgotPassword.getDateExpire())){
|
||||
if (email.equalsIgnoreCase(forgotPassword.getEmail())){
|
||||
if (password.equals(confirmPassword)){
|
||||
String passwordRegex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@?*#$%^&+=_!\\-])(?=\\S+$).{8,}$";
|
||||
Pattern pattern = Pattern.compile(passwordRegex);
|
||||
// Vérifier si le mot de passe correspond à l'expression régulière
|
||||
if (pattern.matcher(password).matches()){
|
||||
UserProfil userForgotPassword = userService.utilisateurByEmail(email);
|
||||
userForgotPassword.setNewPassword(password);
|
||||
userService.setUtilisateur(userForgotPassword);
|
||||
return "redirect:/login";
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePassword?link=" + forgotPassword.getLink() + "&passwordSecurity";
|
||||
}
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePassword?link=" + forgotPassword.getLink() + "&passwordNotIdentique";
|
||||
}
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePassword?link=" + forgotPassword.getLink() + "&emailError";
|
||||
}
|
||||
}else{
|
||||
return "redirect:/forgotPassword/changePasswordExpired?link=" + forgotPassword.getLink();
|
||||
}
|
||||
}else{
|
||||
return "security/forgotPassword";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/changePasswordExpired")
|
||||
public String forgotPasswordLinkExpired(Model model, @RequestParam(value = "link", required = true) String link) {
|
||||
// Vérifier si l'utilisateur est déjà authentifié
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!authentication.getName().equals("anonymousUser")){
|
||||
return "redirect:/accueil";
|
||||
}
|
||||
ForgotPassword forgotPassword = forgotPasswordService.getForgotPassword(link);
|
||||
if (forgotPassword != null) {
|
||||
Calendar dateNow = Calendar.getInstance();
|
||||
if (dateNow.before(forgotPassword.getDateExpire())){
|
||||
return "redirect:/forgotPassword/changePassword?link=" + forgotPassword.getLink();
|
||||
}else{
|
||||
return "security/changePasswordExpired";
|
||||
}
|
||||
}else{
|
||||
return "security/forgotPassword";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,6 @@ public class LanguageController {
|
||||
@GetMapping("/change-language")
|
||||
public String changeLanguage(HttpServletRequest request, HttpServletResponse response, Locale locale) {
|
||||
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
|
||||
System.out.println(locale.getLanguage());
|
||||
if (localeResolver != null) {
|
||||
if (locale.getLanguage().equals("en")) {
|
||||
localeResolver.setLocale(request, response, Locale.FRENCH); // Changer la langue en français
|
||||
@@ -31,7 +30,15 @@ public class LanguageController {
|
||||
localeResolver.setLocale(request, response, Locale.ENGLISH); // Changer la langue en anglais
|
||||
}
|
||||
}
|
||||
return "redirect:/";
|
||||
|
||||
String referer = request.getHeader("Referer");
|
||||
|
||||
if (referer != null && !referer.isEmpty()) {
|
||||
return "redirect:" + referer;
|
||||
} else {
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package fr.eni.enchere.dal;
|
||||
|
||||
import fr.eni.enchere.bo.Article;
|
||||
import fr.eni.enchere.bo.SearchArticleCritere;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ArticleRepository {
|
||||
List<Article> findAllArticle();
|
||||
List<Article> searchArticle(SearchArticleCritere critere);
|
||||
Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable);
|
||||
Article findArticleById(int id);
|
||||
List<Article> findArticleByTitle(String title);
|
||||
int saveArticle(Article article);
|
||||
|
||||
@@ -8,6 +8,9 @@ import fr.eni.enchere.controllers.AccueilController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
@@ -87,7 +90,7 @@ public class ArticleRepositoryImpl implements ArticleRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Article> searchArticle(SearchArticleCritere critere) {
|
||||
public Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable) {
|
||||
StringBuilder sql = new StringBuilder("SELECT DISTINCT a.*, u.* FROM ARTICLES_VENDUS a ");
|
||||
sql.append("JOIN UTILISATEURS u ON a.no_utilisateur = u.no_utilisateur ");
|
||||
sql.append("LEFT JOIN ENCHERES e ON a.no_article = e.no_article ");
|
||||
@@ -165,8 +168,104 @@ public class ArticleRepositoryImpl implements ArticleRepository {
|
||||
sql.append(")");
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
// Compte le nombre total d'articles
|
||||
int totalCount = countArticlePageable(critere);
|
||||
|
||||
// Ajoute la pagination à la requête SQL
|
||||
sql.append(" LIMIT ? OFFSET ?");
|
||||
params.add(pageable.getPageSize());
|
||||
params.add(pageable.getOffset());
|
||||
|
||||
// Exécute la requête paginée
|
||||
List<Article> articles = jdbcTemplate.query(sql.toString(), new HomeArticleRowMapper(), params.toArray());
|
||||
|
||||
// Crée une Page<Article> à partir des résultats et des informations de pagination
|
||||
return new PageImpl<>(articles, pageable, totalCount);
|
||||
}
|
||||
|
||||
public int countArticlePageable(SearchArticleCritere critere) {
|
||||
StringBuilder sql = new StringBuilder("SELECT COUNT(a.no_article) FROM ARTICLES_VENDUS a ");
|
||||
sql.append("JOIN UTILISATEURS u ON a.no_utilisateur = u.no_utilisateur ");
|
||||
sql.append("LEFT JOIN ENCHERES e ON a.no_article = e.no_article ");
|
||||
sql.append("WHERE 1 = 1 AND a.isDelete = 0");
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (critere.getNoCategorie() != null) {
|
||||
sql.append(" AND a.no_categorie = ?");
|
||||
params.add(critere.getNoCategorie());
|
||||
}
|
||||
if (critere.getTitle() != null && !critere.getTitle().isEmpty()) {
|
||||
sql.append(" AND a.nom_article LIKE ?");
|
||||
params.add('%' + critere.getTitle() + '%');
|
||||
}
|
||||
|
||||
if (critere.getVenteOptions() != null && critere.getVenteOptions().length > 0) {
|
||||
sql.append(" AND (");
|
||||
boolean isFirstCondition = true;
|
||||
for (String option : critere.getVenteOptions()) {
|
||||
if (option.equals("venteEnCours")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
|
||||
isFirstCondition = false;
|
||||
}
|
||||
if (option.equals("ventesNonDebutees")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (a.date_debut_encheres > NOW()) ");
|
||||
isFirstCondition = false;
|
||||
}
|
||||
if (option.equals("ventesTerminees")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (a.date_fin_encheres < NOW()) ");
|
||||
isFirstCondition = false;
|
||||
}
|
||||
}
|
||||
sql.append(") AND a.no_utilisateur = ?");
|
||||
params.add(critere.getNoVendeur());
|
||||
}
|
||||
|
||||
if (critere.getAchatOptions() != null && critere.getAchatOptions().length > 0) {
|
||||
sql.append(" AND (");
|
||||
boolean isFirstCondition = true;
|
||||
for (String option : critere.getAchatOptions()) {
|
||||
if (option.equals("encheresOuvertes")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
|
||||
isFirstCondition = false;
|
||||
}
|
||||
if (option.equals("enchereEnCours")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (e.no_utilisateur = ? AND a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
|
||||
isFirstCondition = false;
|
||||
params.add(critere.getNoVendeur());
|
||||
}
|
||||
if (option.equals("enchereRemportees")) {
|
||||
if (!isFirstCondition) {
|
||||
sql.append(" OR ");
|
||||
}
|
||||
sql.append(" (e.no_utilisateur = ? AND e.montant_enchere = (SELECT MAX(montant_enchere) FROM ENCHERES WHERE no_article = a.no_article)) ");
|
||||
isFirstCondition = false;
|
||||
params.add(critere.getNoVendeur());
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
}
|
||||
|
||||
// Exécute la requête paginée
|
||||
int articles = jdbcTemplate.queryForObject(sql.toString(), Integer.class, params.toArray());
|
||||
|
||||
// Retourne le nombre d'articles
|
||||
return articles;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package fr.eni.enchere.dal;
|
||||
|
||||
import fr.eni.enchere.bo.ForgotPassword;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ForgotPasswordRepository {
|
||||
|
||||
ForgotPassword getForgotPasswordByLink(String link);
|
||||
void setForgotPasswords(String email);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package fr.eni.enchere.dal;
|
||||
|
||||
import fr.eni.enchere.bo.ForgotPassword;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Calendar;
|
||||
|
||||
@Repository
|
||||
@Primary
|
||||
public class ForgotPasswordRepositoryImpl implements ForgotPasswordRepository {
|
||||
|
||||
static final String keychain = "0123456789azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN" ;
|
||||
static SecureRandom randomValue = new SecureRandom();
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender javaMailSender;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||
|
||||
public ForgotPasswordRepositoryImpl(JavaMailSender javaMailSender, JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
this.javaMailSender = javaMailSender;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
|
||||
}
|
||||
|
||||
public class ForgotPasswordRowMapper implements RowMapper<ForgotPassword> {
|
||||
@Override
|
||||
public ForgotPassword mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
ForgotPassword forgotPassword = new ForgotPassword();
|
||||
forgotPassword.setId(rs.getInt("id"));
|
||||
forgotPassword.setEmail(rs.getString("email"));
|
||||
forgotPassword.setLink(rs.getString("lien"));
|
||||
Calendar dateCreated = Calendar.getInstance();
|
||||
Calendar dateExpired = Calendar.getInstance();
|
||||
dateCreated.setTime(rs.getDate("dateCreate"));
|
||||
dateExpired.setTime(rs.getDate("dateExpire"));
|
||||
forgotPassword.setDateCreate(dateCreated);
|
||||
forgotPassword.setDateExpire(dateExpired);
|
||||
return forgotPassword;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgotPassword getForgotPasswordByLink(String link) {
|
||||
String sql = "SELECT * FROM FORGOT WHERE lien = ?";
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, new ForgotPasswordRepositoryImpl.ForgotPasswordRowMapper(), link);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
// Aucun résultat trouvé, retourne null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForgotPasswords(String email) {
|
||||
//Générer un code
|
||||
StringBuilder linkCreate = new StringBuilder(50);
|
||||
for (int i = 0; i < 50; i ++){
|
||||
linkCreate.append(keychain.charAt(randomValue.nextInt(keychain.length())));
|
||||
}
|
||||
//Récupérer la date actuelle plus 10 minutes en plus
|
||||
Calendar dateNow = Calendar.getInstance();
|
||||
Calendar dateAgo = Calendar.getInstance();
|
||||
dateAgo.add(Calendar.MINUTE, 10);
|
||||
//Ajouter en base de donnée
|
||||
String sql = "INSERT INTO FORGOT (email, lien, dateCreate, dateExpire) VALUES (:email, :lien, :dateCreate, :dateExpire)";
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource();
|
||||
parameters.addValue("email", email);
|
||||
parameters.addValue("lien", linkCreate.toString());
|
||||
parameters.addValue("dateCreate", dateNow.getTime());
|
||||
parameters.addValue("dateExpire", dateAgo.getTime());
|
||||
namedParameterJdbcTemplate.update(sql, parameters);
|
||||
//Envoyer un email
|
||||
String link = "https://eni.enchere.horya.fr/forgotPassword?link=" + linkCreate.toString();
|
||||
String linkLocal = "http://localhost:8800/forgotPassword?link=" + linkCreate.toString();
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom("support@horya.fr");
|
||||
message.setTo(email);
|
||||
message.setSubject("ENI Enchere - Demmande de changement de mot de passe");
|
||||
message.setText("Bonjour,\n\nVous avez demandé une réinitialisation de votre mot de passe. Veuillez utiliser le lien suivant pour procéder à la réinitialisation : " + link + "\n\n" + linkLocal);
|
||||
javaMailSender.send(message);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ public interface UserRepository {
|
||||
List<UserProfil> findAll();
|
||||
UserProfil findById(int id);
|
||||
UserProfil findByUsername(String username);
|
||||
UserProfil findUserByEmail(String email);
|
||||
List<String> findAllUsernames();
|
||||
List<String> findAllEmail();
|
||||
String findByEmail(String email);
|
||||
void save(UserProfil utilisateur);
|
||||
void updateCredit(float credit, int id);
|
||||
void delete(int id);
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.eni.enchere.dal;
|
||||
import fr.eni.enchere.bo.UserProfil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
@@ -60,6 +61,13 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfil findUserByEmail(String email) {
|
||||
String sql = "SELECT * FROM UTILISATEURS WHERE email = ? AND isDelete = 0";
|
||||
UserProfil user = jdbcTemplate.queryForObject(sql, new UserRowMapper(), email);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> findAllUsernames() {
|
||||
String sql = "SELECT pseudo FROM UTILISATEURS WHERE isDelete = 0";
|
||||
@@ -74,6 +82,18 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findByEmail(String email) {
|
||||
//Vérifie si un email existe dans la base et est valide
|
||||
String sql = "SELECT email FROM UTILISATEURS WHERE isDisabled = 0 AND email = ?";
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, new Object[]{email}, String.class);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
// Aucun résultat trouvé, retourne null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserProfil> findAll() {
|
||||
String sql = "SELECT * FROM UTILISATEURS WHERE isDelete = 0";
|
||||
|
||||
@@ -19,7 +19,7 @@ public class WebSecurityConfig{
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests((requests) -> requests
|
||||
.requestMatchers("/","/accueil", "/login", "/inscription/**", "/searchArticle", "/article/show", "/change-language").permitAll()
|
||||
.requestMatchers("/","/accueil", "/login", "/forgotPassword/**", "/inscription/**", "/searchArticle", "/article/show", "/change-language").permitAll()
|
||||
.requestMatchers("/css/**", "/images/**", "/assets/**", "/img/**", "/js/**", "/assets/**", "/i18n/**").permitAll()
|
||||
.requestMatchers("/profil/**", "/article/new/**", "/article/update", "/article/delete").authenticated()
|
||||
.requestMatchers("/admin").hasRole("ADMIN")
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
spring.servlet.multipart.max-file-size=5MB
|
||||
spring.datasource.url=jdbc:mariadb://91.121.54.36:3306/eni_enchere
|
||||
spring.datasource.username=eni
|
||||
spring.datasource.password=Pa$$w0rd
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
spring.messages.encoding=UTF-8
|
||||
server.port=8800
|
||||
spring.mail.host=smtp.mail.ovh.net
|
||||
spring.mail.port=465
|
||||
spring.mail.username=support@horya.fr
|
||||
spring.mail.password=5AQyQR%zg3qDfnh
|
||||
spring.mail.properties.mail.transport.protocol=smtp
|
||||
spring.mail.properties.mail.smtp.port=465
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.ssl.enable=true
|
||||
@@ -12,12 +12,17 @@ home.sell.finish = My completed sales
|
||||
home.button.search = Search
|
||||
home.button.lang = Passer en fran\u00E7ais
|
||||
home.button.lang2 = EN
|
||||
home.button.first = First
|
||||
home.button.end = End
|
||||
home.button.next = Next
|
||||
home.button.previous = Previous
|
||||
home.credit = My credits:
|
||||
home.nav.enchere = Auctions
|
||||
home.nav.vend = Sell an item
|
||||
home.nav.login = Register / Log in
|
||||
home.profil.profil = Profile
|
||||
home.profil.logout = Log out
|
||||
home.profil.bank = Credit
|
||||
home.article.sellprice = Sale price:
|
||||
home.article.seller = Seller:
|
||||
home.article.end = End of auction:
|
||||
|
||||
@@ -12,12 +12,17 @@ home.sell.finish = Mes ventes termin\u00E9es
|
||||
home.button.search = Recherche
|
||||
home.button.lang = Switch to English
|
||||
home.button.lang2 = FR
|
||||
home.button.first = D\u00e9but
|
||||
home.button.end = Fin
|
||||
home.button.next = Suivant
|
||||
home.button.previous = Pr\u00e9cedent
|
||||
home.credit = Mes cr\u00e9dits :
|
||||
home.nav.enchere = Encheres
|
||||
home.nav.vend = Vendre un article
|
||||
home.nav.login = S'inscrire / Se connecter
|
||||
home.profil.profil = Profil
|
||||
home.profil.logout = D\u00e9connexion
|
||||
home.profil.bank = Cr\u00e9diter
|
||||
home.article.sellprice = Prix de vente:
|
||||
home.article.seller = Vendeur:
|
||||
home.article.end = Fin de l'ench\u00E8re:
|
||||
|
||||
@@ -30,3 +30,7 @@
|
||||
.navbar-nav .nav-item.active .nav-link {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#container-main {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
BIN
src/main/resources/static/images/articles/1.jpg
Normal file
|
After Width: | Height: | Size: 339 KiB |
BIN
src/main/resources/static/images/articles/10.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
src/main/resources/static/images/articles/100.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 572 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 253 KiB |
BIN
src/main/resources/static/images/articles/2.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 957 B |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 976 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 391 KiB |
BIN
src/main/resources/static/images/articles/25.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/main/resources/static/images/articles/26.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
src/main/resources/static/images/articles/27.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
src/main/resources/static/images/articles/28.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/main/resources/static/images/articles/29.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
src/main/resources/static/images/articles/3.jpg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
src/main/resources/static/images/articles/30.jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
src/main/resources/static/images/articles/31.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
src/main/resources/static/images/articles/32.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
src/main/resources/static/images/articles/33.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/main/resources/static/images/articles/34.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
src/main/resources/static/images/articles/35.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
src/main/resources/static/images/articles/36.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
src/main/resources/static/images/articles/37.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
src/main/resources/static/images/articles/38.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
src/main/resources/static/images/articles/39.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/main/resources/static/images/articles/4.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
src/main/resources/static/images/articles/40.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/main/resources/static/images/articles/41.jpg
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
src/main/resources/static/images/articles/42.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
src/main/resources/static/images/articles/43.jpg
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
src/main/resources/static/images/articles/44.jpg
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
src/main/resources/static/images/articles/45.jpg
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
src/main/resources/static/images/articles/46.jpg
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
src/main/resources/static/images/articles/47.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
src/main/resources/static/images/articles/48.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
src/main/resources/static/images/articles/49.jpg
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
src/main/resources/static/images/articles/5.jpg
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
src/main/resources/static/images/articles/50.jpg
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
src/main/resources/static/images/articles/51.jpg
Normal file
|
After Width: | Height: | Size: 632 KiB |
BIN
src/main/resources/static/images/articles/52.jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
src/main/resources/static/images/articles/53.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
src/main/resources/static/images/articles/54.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
src/main/resources/static/images/articles/55.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
src/main/resources/static/images/articles/56.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/static/images/articles/57.jpg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
src/main/resources/static/images/articles/58.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/static/images/articles/59.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/static/images/articles/6.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
src/main/resources/static/images/articles/60.jpg
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/main/resources/static/images/articles/61.jpg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/main/resources/static/images/articles/62.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/static/images/articles/63.jpg
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/main/resources/static/images/articles/64.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/main/resources/static/images/articles/65.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/main/resources/static/images/articles/66.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/main/resources/static/images/articles/67.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/main/resources/static/images/articles/68.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/images/articles/69.jpg
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/main/resources/static/images/articles/7.jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
src/main/resources/static/images/articles/70.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/main/resources/static/images/articles/71.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/images/articles/72.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/static/images/articles/73.jpg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/main/resources/static/images/articles/74.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/main/resources/static/images/articles/75.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/static/images/articles/76.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |