diff --git a/NauJava/pom.xml b/NauJava/pom.xml index 26e9ab9..cb24a68 100644 --- a/NauJava/pom.xml +++ b/NauJava/pom.xml @@ -1,12 +1,12 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent 4.0.3 - + ru.andrmuratov NauJava @@ -30,6 +30,7 @@ 21 + org.springframework.boot spring-boot-starter-actuator @@ -37,9 +38,52 @@ org.springframework.boot - spring-boot-starter-actuator-test + spring-boot-starter-data-jpa + + + + org.postgresql + postgresql + runtime + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + org.springframework.boot + spring-boot-starter-security + + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity6 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test test + @@ -51,4 +95,4 @@ - + \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/NauJavaApplication.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/NauJavaApplication.java index 78c589e..d34bcb9 100644 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/NauJavaApplication.java +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/NauJavaApplication.java @@ -5,9 +5,7 @@ @SpringBootApplication public class NauJavaApplication { - public static void main(String[] args) { SpringApplication.run(NauJavaApplication.class, args); } - -} +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/config/Config.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/config/Config.java index cf505fd..14b469a 100644 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/config/Config.java +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/config/Config.java @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; -import org.springframework.boot.CommandLineRunner; +//import org.springframework.boot.CommandLineRunner; import org.springframework.beans.factory.annotation.Value; import jakarta.annotation.PostConstruct; import ru.andrmuratov.NauJava.entity.Task; @@ -33,8 +33,8 @@ public List taskContainer() { return new ArrayList<>(); } - @Bean - public CommandLineRunner run(CommandProcessor processor) { - return args -> processor.start(); - } +// @Bean +// public CommandLineRunner run(CommandProcessor processor) { +// return args -> processor.start(); +// } } \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/LoginController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/LoginController.java new file mode 100644 index 0000000..c05ec71 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/LoginController.java @@ -0,0 +1,13 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class LoginController { + + @GetMapping("/login") + public String loginPage() { + return "login"; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/RegistrationController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/RegistrationController.java new file mode 100644 index 0000000..950354f --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/RegistrationController.java @@ -0,0 +1,45 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +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 ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; + +@Controller +public class RegistrationController { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Autowired + public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + @GetMapping("/registration") + public String registrationPage() { + return "registration"; + } + + @PostMapping("/registration") + public String registerUser(User user, Model model) { + if (userRepository.findByUsername(user.getUsername()).isPresent()) { + model.addAttribute("message", "User already exists"); + return "registration"; + } + + user.setPassword(passwordEncoder.encode(user.getPassword())); + + if (user.getRole() == null || user.getRole().isEmpty()) { + user.setRole("USER"); + } + + userRepository.save(user); + return "redirect:/login"; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportController.java new file mode 100644 index 0000000..183d920 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportController.java @@ -0,0 +1,47 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.andrmuratov.NauJava.entity.Report; +import ru.andrmuratov.NauJava.entity.ReportStatus; +import ru.andrmuratov.NauJava.service.ReportService; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/reports") +public class ReportController { + + private final ReportService reportService; + + @Autowired + public ReportController(ReportService reportService) { + this.reportService = reportService; + } + + @PostMapping + public ResponseEntity> createReport() { + Long id = reportService.createReport(); + reportService.generateReportAsync(id); + Map response = new HashMap<>(); + response.put("reportId", id); + return ResponseEntity.ok(response); + } + + @GetMapping("/{id}") + public ResponseEntity getReport(@PathVariable Long id) { + Report report = reportService.getReportById(id); + if (report == null) { + return ResponseEntity.notFound().build(); + } + if (report.getStatus() == ReportStatus.CREATED) { + return ResponseEntity.ok("Report is being generated"); + } + if (report.getStatus() == ReportStatus.ERROR) { + return ResponseEntity.status(500).body(report.getContent()); + } + return ResponseEntity.ok().contentType(org.springframework.http.MediaType.TEXT_HTML).body(report.getContent()); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportViewController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportViewController.java new file mode 100644 index 0000000..ddf4121 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/ReportViewController.java @@ -0,0 +1,15 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/reports") +public class ReportViewController { + + @GetMapping + public String reportPage() { + return "report"; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserController.java new file mode 100644 index 0000000..e2faa79 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserController.java @@ -0,0 +1,36 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + private final UserRepository userRepository; + + @Autowired + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @GetMapping("/by-username") + public Optional findByUsername(@RequestParam String username) { + return userRepository.findByUsername(username); + } + + @GetMapping("/by-role") + public List findByRole(@RequestParam String role) { + return userRepository.findByRoleJPQL(role); + } + + @GetMapping("/by-role-email") + public List findByRoleAndEmail(@RequestParam String role, @RequestParam String email) { + return userRepository.findByRoleAndEmailContaining(role, email); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserViewController.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserViewController.java new file mode 100644 index 0000000..cfbcc0a --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/controller/UserViewController.java @@ -0,0 +1,28 @@ +package ru.andrmuratov.NauJava.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; + +@Controller +@RequestMapping("/users") +public class UserViewController { + + private final UserRepository userRepository; + + @Autowired + public UserViewController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @GetMapping("/list") + public String listUsers(Model model) { + Iterable users = userRepository.findAll(); + model.addAttribute("users", users); + return "user-list"; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/CrudRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/CrudRepository.java deleted file mode 100644 index 2fbaf77..0000000 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/CrudRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.andrmuratov.NauJava.dao; - -public interface CrudRepository { - void create(T entity); - T read(ID id); - void update(T entity); - void delete(ID id); -} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/TaskRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/TaskRepository.java deleted file mode 100644 index 379cec9..0000000 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/TaskRepository.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.andrmuratov.NauJava.dao; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import ru.andrmuratov.NauJava.entity.Task; - -import java.util.List; - -@Component -public class TaskRepository implements CrudRepository { - - private final List taskContainer; - - @Autowired - public TaskRepository(List taskContainer) { - this.taskContainer = taskContainer; - } - - @Override - public void create(Task task) { - taskContainer.add(task); - } - - @Override - public Task read(Long id) { - for (Task task : taskContainer) { - if (task.getId().equals(id)) { - return task; - } - } - return null; - } - - @Override - public void update(Task task) { - for (int i = 0; i < taskContainer.size(); i++) { - if (taskContainer.get(i).getId().equals(task.getId())) { - taskContainer.set(i, task); - return; - } - } - } - - @Override - public void delete(Long id) { - taskContainer.removeIf(task -> task.getId().equals(id)); - } - - // Дополнительный метод для получения всех задач - public List findAll() { - return taskContainer; - } -} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryCustom.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryCustom.java new file mode 100644 index 0000000..bac4a79 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryCustom.java @@ -0,0 +1,9 @@ +package ru.andrmuratov.NauJava.dao; + +import ru.andrmuratov.NauJava.entity.User; +import java.util.List; + +public interface UserRepositoryCustom { + List findByNameCriteria(String name); + List findByRoleCriteria(String role); +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryImpl.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryImpl.java new file mode 100644 index 0000000..ff7e2b7 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/dao/UserRepositoryImpl.java @@ -0,0 +1,43 @@ +package ru.andrmuratov.NauJava.dao; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import ru.andrmuratov.NauJava.entity.User; + +import java.util.List; + +@Repository +public class UserRepositoryImpl implements UserRepositoryCustom { + + private final EntityManager entityManager; + + @Autowired + public UserRepositoryImpl(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public List findByNameCriteria(String name) { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(User.class); + Root root = cq.from(User.class); + Predicate predicate = cb.equal(root.get("name"), name); + cq.select(root).where(predicate); + return entityManager.createQuery(cq).getResultList(); + } + + @Override + public List findByRoleCriteria(String role) { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(User.class); + Root root = cq.from(User.class); + Predicate predicate = cb.equal(root.get("role"), role); + cq.select(root).where(predicate); + return entityManager.createQuery(cq).getResultList(); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Category.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Category.java new file mode 100644 index 0000000..f3e8e95 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Category.java @@ -0,0 +1,30 @@ +package ru.andrmuratov.NauJava.entity; + +import jakarta.persistence.*; +import java.util.List; + +@Entity +@Table(name = "categories") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + private String description; + + @OneToMany(mappedBy = "category") + private List tasks; + + public Category() {} + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public List getTasks() { return tasks; } + public void setTasks(List tasks) { this.tasks = tasks; } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Comment.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Comment.java new file mode 100644 index 0000000..092070c --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Comment.java @@ -0,0 +1,31 @@ +package ru.andrmuratov.NauJava.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 1000) + private String text; + + private LocalDateTime createdAt; + + @ManyToOne + @JoinColumn(name = "task_id", nullable = false) + private Task task; + + public Comment() {} + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getText() { return text; } + public void setText(String text) { this.text = text; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + public Task getTask() { return task; } + public void setTask(Task task) { this.task = task; } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Report.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Report.java new file mode 100644 index 0000000..76d0173 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Report.java @@ -0,0 +1,45 @@ +package ru.andrmuratov.NauJava.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "reports") +public class Report { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ReportStatus status; + + @Column(columnDefinition = "TEXT") + private String content; + + public Report() {} + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ReportStatus getStatus() { + return status; + } + + public void setStatus(ReportStatus status) { + this.status = status; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/ReportStatus.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/ReportStatus.java new file mode 100644 index 0000000..d897aaa --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/ReportStatus.java @@ -0,0 +1,7 @@ +package ru.andrmuratov.NauJava.entity; + +public enum ReportStatus { + CREATED, + COMPLETED, + ERROR +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Tag.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Tag.java new file mode 100644 index 0000000..0292a28 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Tag.java @@ -0,0 +1,26 @@ +package ru.andrmuratov.NauJava.entity; + +import jakarta.persistence.*; +import java.util.Set; + +@Entity +@Table(name = "tags") +public class Tag { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + @ManyToMany(mappedBy = "tags") + private Set tasks; + + public Tag() {} + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public Set getTasks() { return tasks; } + public void setTasks(Set tasks) { this.tasks = tasks; } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Task.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Task.java index de62452..abc3fb5 100644 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Task.java +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/Task.java @@ -1,66 +1,77 @@ package ru.andrmuratov.NauJava.entity; +import jakarta.persistence.*; import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +@Entity +@Table(name = "tasks") public class Task { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(nullable = false) private String title; + private String description; - private Priority priority; - private Status status; + + @Column(nullable = false) + private String status; + + private String priority; + private LocalDateTime deadline; + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; - public Task() {} + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; + @OneToMany(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments; - public Task(Long id, String title, String description, Priority priority, Status status, LocalDateTime deadline) { - this.id = id; - this.title = title; - this.description = description; - this.priority = priority; - this.status = status; - this.deadline = deadline; - } + @ManyToMany + @JoinTable( + name = "task_tag", + joinColumns = @JoinColumn(name = "task_id"), + inverseJoinColumns = @JoinColumn(name = "tag_id") + ) + private Set tags; + + public Task() {} public Long getId() { return id; } public void setId(Long id) { this.id = id; } - public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } - public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } - - public Priority getPriority() { return priority; } - public void setPriority(Priority priority) { this.priority = priority; } - - public Status getStatus() { return status; } - public void setStatus(Status status) { this.status = status; } - + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getPriority() { return priority; } + public void setPriority(String priority) { this.priority = priority; } public LocalDateTime getDeadline() { return deadline; } public void setDeadline(LocalDateTime deadline) { this.deadline = deadline; } - + public User getUser() { return user; } + public void setUser(User user) { this.user = user; } + public Category getCategory() { return category; } + public void setCategory(Category category) { this.category = category; } + public List getComments() { return comments; } + public void setComments(List comments) { this.comments = comments; } + public Set getTags() { return tags; } + public void setTags(Set tags) { this.tags = tags; } public enum Priority { LOW, MEDIUM, HIGH } - public enum Status { TODO, IN_PROGRESS, DONE } - - - @Override - public String toString() { - return "Task{" + - "id=" + id + - ", title='" + title + '\'' + - ", priority=" + priority + - ", status=" + status + - ", deadline=" + deadline + - '}'; - } } \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/User.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/User.java new file mode 100644 index 0000000..17920b6 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/entity/User.java @@ -0,0 +1,43 @@ +package ru.andrmuratov.NauJava.entity; + +import jakarta.persistence.*; +import java.util.List; +import java.util.Set; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private String email; + + @Column(nullable = false) + private String role; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List tasks; + + public User() {} + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public List getTasks() { return tasks; } + public void setTasks(List tasks) { this.tasks = tasks; } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/exception/GlobalExceptionHandler.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..6307c34 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/exception/GlobalExceptionHandler.java @@ -0,0 +1,37 @@ +package ru.andrmuratov.NauJava.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + @ResponseBody + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Map handleException(Exception e) { + Map error = new HashMap<>(); + error.put("timestamp", LocalDateTime.now()); + error.put("status", 500); + error.put("error", "Internal Server Error"); + error.put("message", e.getMessage()); + return error; + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleBadRequest(Exception e) { + Map error = new HashMap<>(); + error.put("timestamp", LocalDateTime.now()); + error.put("status", 400); + error.put("error", "Bad Request"); + error.put("message", e.getMessage()); + return error; + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CategoryRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CategoryRepository.java new file mode 100644 index 0000000..050c2a6 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CategoryRepository.java @@ -0,0 +1,9 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.entity.Category; + +@RepositoryRestResource(path = "categories") +public interface CategoryRepository extends CrudRepository { +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CommentRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CommentRepository.java new file mode 100644 index 0000000..a8ec219 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/CommentRepository.java @@ -0,0 +1,9 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.entity.Comment; + +@RepositoryRestResource(path = "comments") +public interface CommentRepository extends CrudRepository { +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/ReportRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/ReportRepository.java new file mode 100644 index 0000000..da70776 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/ReportRepository.java @@ -0,0 +1,7 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.entity.Report; + +public interface ReportRepository extends CrudRepository { +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TagRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TagRepository.java new file mode 100644 index 0000000..6faf4ea --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TagRepository.java @@ -0,0 +1,9 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.entity.Tag; + +@RepositoryRestResource(path = "tags") +public interface TagRepository extends CrudRepository { +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TaskRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TaskRepository.java new file mode 100644 index 0000000..280d8d6 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/TaskRepository.java @@ -0,0 +1,16 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.entity.Task; +import java.util.List; + +@RepositoryRestResource(path = "tasks") +public interface TaskRepository extends CrudRepository { + List findByStatus(String status); + List findByUserId(Long userId); + + @Query("SELECT t FROM Task t WHERE t.user.email = :email") + List findByUserEmailJPQL(String email); +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/UserRepository.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/UserRepository.java new file mode 100644 index 0000000..749c5a9 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.andrmuratov.NauJava.repository; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.repository.CrudRepository; +import ru.andrmuratov.NauJava.dao.UserRepositoryCustom; +import ru.andrmuratov.NauJava.entity.User; +import java.util.List; +import java.util.Optional; + +@RepositoryRestResource(path = "users") +public interface UserRepository extends CrudRepository, UserRepositoryCustom { + + Optional findByUsername(String username); + + List findByRoleAndEmailContaining(String role, String emailDomain); + + @Query("SELECT u FROM User u WHERE u.role = :role") + List findByRoleJPQL(String role); +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/security/CustomUserDetailsService.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/security/CustomUserDetailsService.java new file mode 100644 index 0000000..cca33b9 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/security/CustomUserDetailsService.java @@ -0,0 +1,35 @@ +package ru.andrmuratov.NauJava.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; + +import java.util.Collections; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Autowired + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole())) + ); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/security/SecurityConfig.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/security/SecurityConfig.java new file mode 100644 index 0000000..7cf19a5 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/security/SecurityConfig.java @@ -0,0 +1,53 @@ +package ru.andrmuratov.NauJava.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + private CustomUserDetailsService userDetailsService; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + // Публичные пути + .requestMatchers("/", "/login", "/registration", "/css/**", "/js/**").permitAll() + // Swagger UI только для ADMIN + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").hasRole("ADMIN") + // REST API требует аутентификации + .requestMatchers("/api/**").authenticated() + // Все остальные пути требуют аутентификации + .anyRequest().authenticated() + ) + .formLogin(form -> form + .loginPage("/login") + .loginProcessingUrl("/login") + .defaultSuccessUrl("/users/list", true) + .failureUrl("/login?error=true") + .permitAll() + ) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout=true") + .permitAll() + ) + .csrf(csrf -> csrf.disable()); + + return http.build(); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportService.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportService.java new file mode 100644 index 0000000..4b2e88d --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportService.java @@ -0,0 +1,9 @@ +package ru.andrmuratov.NauJava.service; + +import ru.andrmuratov.NauJava.entity.Report; + +public interface ReportService { + Report getReportById(Long id); + Long createReport(); + void generateReportAsync(Long reportId); +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportServiceImpl.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportServiceImpl.java new file mode 100644 index 0000000..5e4e23b --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/ReportServiceImpl.java @@ -0,0 +1,86 @@ +package ru.andrmuratov.NauJava.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.andrmuratov.NauJava.entity.Report; +import ru.andrmuratov.NauJava.entity.ReportStatus; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.ReportRepository; +import ru.andrmuratov.NauJava.repository.UserRepository; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.StreamSupport; + +@Service +public class ReportServiceImpl implements ReportService { + + private final ReportRepository reportRepository; + private final UserRepository userRepository; + + @Autowired + public ReportServiceImpl(ReportRepository reportRepository, UserRepository userRepository) { + this.reportRepository = reportRepository; + this.userRepository = userRepository; + } + + @Override + public Report getReportById(Long id) { + return reportRepository.findById(id).orElse(null); + } + + @Override + public Long createReport() { + Report report = new Report(); + report.setStatus(ReportStatus.CREATED); + reportRepository.save(report); + return report.getId(); + } + + @Override + public void generateReportAsync(Long reportId) { + CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + long startTime = System.currentTimeMillis(); + + long[] userCount = new long[1]; + Thread countUsersThread = new Thread(() -> { + userCount[0] = userRepository.count(); + }); + + List[] usersList = new List[1]; + Thread getListThread = new Thread(() -> { + usersList[0] = StreamSupport.stream(userRepository.findAll().spliterator(), false).toList(); + }); + + countUsersThread.start(); + getListThread.start(); + + countUsersThread.join(); + getListThread.join(); + + long elapsed = System.currentTimeMillis() - startTime; + + Report report = reportRepository.findById(reportId).orElseThrow(); + StringBuilder html = new StringBuilder(); + html.append("Report"); + html.append("

Report

"); + html.append(""); + html.append(""); + html.append(""); + html.append(""); + html.append("
MetricValueTime (ms)
Users count").append(userCount[0]).append("").append(elapsed).append("
Users list").append(usersList[0].size()).append("").append(elapsed).append("
Total time").append(elapsed).append(" ms
"); + + report.setContent(html.toString()); + report.setStatus(ReportStatus.COMPLETED); + reportRepository.save(report); + + } catch (Exception e) { + Report report = reportRepository.findById(reportId).orElseThrow(); + report.setStatus(ReportStatus.ERROR); + report.setContent("Error: " + e.getMessage()); + reportRepository.save(report); + } + }); + } +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/TaskServiceImpl.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/TaskServiceImpl.java index 72450cc..ea56f28 100644 --- a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/TaskServiceImpl.java +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/TaskServiceImpl.java @@ -2,18 +2,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.andrmuratov.NauJava.dao.TaskRepository; +import ru.andrmuratov.NauJava.repository.TaskRepository; import ru.andrmuratov.NauJava.entity.Task; import java.time.LocalDateTime; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.StreamSupport; @Service public class TaskServiceImpl implements TaskService { private final TaskRepository taskRepository; - private final AtomicLong idGenerator = new AtomicLong(1); @Autowired public TaskServiceImpl(TaskRepository taskRepository) { @@ -23,46 +22,44 @@ public TaskServiceImpl(TaskRepository taskRepository) { @Override public void createTask(String title, String description, Task.Priority priority, Task.Status status) { Task task = new Task(); - task.setId(idGenerator.getAndIncrement()); task.setTitle(title); task.setDescription(description); - task.setPriority(priority); - task.setStatus(status); + task.setPriority(priority.name()); + task.setStatus(status.name()); task.setDeadline(LocalDateTime.now().plusDays(7)); - taskRepository.create(task); + taskRepository.save(task); } @Override public Task findById(Long id) { - return taskRepository.read(id); + return taskRepository.findById(id).orElse(null); } @Override public void deleteById(Long id) { - taskRepository.delete(id); + taskRepository.deleteById(id); } @Override public void updateTask(Long id, String title, String description, Task.Priority priority, Task.Status status) { - Task task = taskRepository.read(id); + Task task = taskRepository.findById(id).orElse(null); if (task != null) { task.setTitle(title); task.setDescription(description); - task.setPriority(priority); - task.setStatus(status); - taskRepository.update(task); + task.setPriority(priority.name()); + task.setStatus(status.name()); + taskRepository.save(task); } } @Override public List findAll() { - return taskRepository.findAll(); + return StreamSupport.stream(taskRepository.findAll().spliterator(), false) + .toList(); } @Override public List findByStatus(Task.Status status) { - return taskRepository.findAll().stream() - .filter(task -> task.getStatus() == status) - .toList(); + return taskRepository.findByStatus(status.name()); } } \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserService.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserService.java new file mode 100644 index 0000000..41a49e0 --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserService.java @@ -0,0 +1,6 @@ +package ru.andrmuratov.NauJava.service; + +public interface UserService { + void createUserWithTask(String name, String email, String role, String taskTitle); + void deleteUserWithTasks(Long userId); +} \ No newline at end of file diff --git a/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserServiceImpl.java b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserServiceImpl.java new file mode 100644 index 0000000..7da364b --- /dev/null +++ b/NauJava/src/main/java/ru/andrmuratov/NauJava/service/UserServiceImpl.java @@ -0,0 +1,53 @@ +package ru.andrmuratov.NauJava.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.andrmuratov.NauJava.entity.Task; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.TaskRepository; +import ru.andrmuratov.NauJava.repository.UserRepository; + +import java.time.LocalDateTime; + +@Service +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final TaskRepository taskRepository; + private final PasswordEncoder passwordEncoder; + + @Autowired + public UserServiceImpl(UserRepository userRepository, + TaskRepository taskRepository, + PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.taskRepository = taskRepository; + this.passwordEncoder = passwordEncoder; + } + + @Override + @Transactional + public void createUserWithTask(String username, String email, String role, String taskTitle) { + User user = new User(); + user.setUsername(username); + user.setEmail(email); + user.setRole(role); + user.setPassword(passwordEncoder.encode("defaultPassword")); + userRepository.save(user); + + Task task = new Task(); + task.setTitle(taskTitle); + task.setStatus("TODO"); + task.setDeadline(LocalDateTime.now().plusDays(7)); + task.setUser(user); + taskRepository.save(task); + } + + @Override + @Transactional + public void deleteUserWithTasks(Long userId) { + userRepository.deleteById(userId); + } +} \ No newline at end of file diff --git a/NauJava/src/main/resources/application.properties b/NauJava/src/main/resources/application.properties index 8eb9f8b..b9c2b85 100644 --- a/NauJava/src/main/resources/application.properties +++ b/NauJava/src/main/resources/application.properties @@ -1,3 +1,14 @@ app.name=TaskFlow -app.version=1.0.0 +app.version=2.0.0 + +spring.datasource.url=jdbc:postgresql://localhost:5432/taskflow +spring.datasource.username=postgres +spring.datasource.password=naumen +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.format_sql=true + management.endpoints.web.exposure.include=* \ No newline at end of file diff --git a/NauJava/src/main/resources/templates/login.html b/NauJava/src/main/resources/templates/login.html new file mode 100644 index 0000000..47baec9 --- /dev/null +++ b/NauJava/src/main/resources/templates/login.html @@ -0,0 +1,34 @@ + + + + + Login + + + +

Login

+
Invalid username or password
+
You have been logged out
+
+
+ + +
+
+ + +
+ +
+

Don't have an account? Register

+ + \ No newline at end of file diff --git a/NauJava/src/main/resources/templates/registration.html b/NauJava/src/main/resources/templates/registration.html new file mode 100644 index 0000000..a84ad35 --- /dev/null +++ b/NauJava/src/main/resources/templates/registration.html @@ -0,0 +1,43 @@ + + + + + Registration + + + +

Registration

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

Already have an account? Login

+ + \ No newline at end of file diff --git a/NauJava/src/main/resources/templates/report.html b/NauJava/src/main/resources/templates/report.html new file mode 100644 index 0000000..b646c59 --- /dev/null +++ b/NauJava/src/main/resources/templates/report.html @@ -0,0 +1,27 @@ + + + + + Report + + +

Report Generation

+ +
+ + + \ No newline at end of file diff --git a/NauJava/src/main/resources/templates/user-list.html b/NauJava/src/main/resources/templates/user-list.html new file mode 100644 index 0000000..a6c4590 --- /dev/null +++ b/NauJava/src/main/resources/templates/user-list.html @@ -0,0 +1,40 @@ + + + + + User List + + + +

User List

+
+ + +
+ +
+
+ + + + + + + + + + + + + +
IDNameEmailRole
+
+Swagger UI | +Actuator + + \ No newline at end of file diff --git a/NauJava/src/test/java/ru/andrmuratov/NauJava/UserRepositoryTest.java b/NauJava/src/test/java/ru/andrmuratov/NauJava/UserRepositoryTest.java new file mode 100644 index 0000000..9980a16 --- /dev/null +++ b/NauJava/src/test/java/ru/andrmuratov/NauJava/UserRepositoryTest.java @@ -0,0 +1,46 @@ +package ru.andrmuratov.NauJava; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; + +import java.util.List; +import java.util.UUID; + +@SpringBootTest +class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Test + void testFindByName() { + String name = UUID.randomUUID().toString(); + User user = new User(); + user.setName(name); + user.setEmail(name + "@test.com"); + user.setRole("USER"); + userRepository.save(user); + + List found = userRepository.findByName(name); + Assertions.assertFalse(found.isEmpty()); + Assertions.assertEquals(name, found.get(0).getName()); + } + + @Test + void testCriteriaAPI() { + String name = UUID.randomUUID().toString(); + User user = new User(); + user.setName(name); + user.setEmail(name + "@test.com"); + user.setRole("ADMIN"); + userRepository.save(user); + + List found = userRepository.findByNameCriteria(name); + Assertions.assertFalse(found.isEmpty()); + Assertions.assertEquals(name, found.get(0).getName()); + } +} \ No newline at end of file diff --git a/NauJava/src/test/java/ru/andrmuratov/NauJava/UserServiceTest.java b/NauJava/src/test/java/ru/andrmuratov/NauJava/UserServiceTest.java new file mode 100644 index 0000000..8f766db --- /dev/null +++ b/NauJava/src/test/java/ru/andrmuratov/NauJava/UserServiceTest.java @@ -0,0 +1,35 @@ +package ru.andrmuratov.NauJava; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import ru.andrmuratov.NauJava.entity.User; +import ru.andrmuratov.NauJava.repository.UserRepository; +import ru.andrmuratov.NauJava.service.UserService; + +import java.util.UUID; + +@SpringBootTest +class UserServiceTest { + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + @Test + void testCreateUserWithTask() { + String name = UUID.randomUUID().toString(); + String email = name + "@test.com"; + userService.createUserWithTask(name, email, "USER", "Test Task"); + + User found = userRepository.findByRoleJPQL("USER").stream() + .filter(u -> u.getEmail().equals(email)) + .findFirst() + .orElse(null); + Assertions.assertNotNull(found); + Assertions.assertEquals(name, found.getName()); + } +} \ No newline at end of file