diff --git a/.gitignore b/.gitignore
index cd38e2e7b..7f9709787 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,6 @@ target
logs
attachments
*.patch
+.env
diff --git a/README.md b/README.md
index 719b268f5..4acf2bc07 100644
--- a/README.md
+++ b/README.md
@@ -27,4 +27,11 @@
- https://habr.com/ru/articles/259055/
Список выполненных задач:
-...
\ No newline at end of file
+1. Understand the project structure (onboarding).
+2. Removed VK and Ya.
+3. Introduce sensitive information to a specific file.
+4. Change of tests from PostgreSQL to in-memory database.
+5. Write tests for all public methods of the ProfileRestController controller.
+6. Refactor the com.javarush.jira.bugtracking.attachment.FileUtil#upload method to use a modern approach to working with the file system.
+7. Add new functionality: adding tags to a task (REST API + implementation on the service).
+8. Add time counting: how much time the task has been in work and testing. Write 2 methods at the service level that take a task as a parameter and return the time spent.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4556ac639..c42d83024 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,6 +152,18 @@
true
+
+ me.paulschwarz
+ spring-dotenv
+ 4.0.0
+
+
+
+
+ com.h2database
+ h2
+ test
+
diff --git a/resources/static/fontawesome/css/all.css b/resources/static/fontawesome/css/all.css
index af5980828..c337f48dd 100644
--- a/resources/static/fontawesome/css/all.css
+++ b/resources/static/fontawesome/css/all.css
@@ -8603,9 +8603,6 @@ readers do not read off random characters that represent icons */
content: "\f3e8";
}
-.fa-vk:before {
- content: "\f189";
-}
.fa-untappd:before {
content: "\f405";
@@ -9955,9 +9952,6 @@ readers do not read off random characters that represent icons */
content: "\f3bc";
}
-.fa-yandex:before {
- content: "\f413";
-}
.fa-readme:before {
content: "\f4d5";
@@ -10183,9 +10177,6 @@ readers do not read off random characters that represent icons */
content: "\f7c6";
}
-.fa-yandex-international:before {
- content: "\f414";
-}
.fa-cc-amex:before {
content: "\f1f3";
diff --git a/resources/view/login.html b/resources/view/login.html
index 8765ca8ff..d49ce5691 100644
--- a/resources/view/login.html
+++ b/resources/view/login.html
@@ -48,14 +48,6 @@ Sign in
type="button">
-
-
-
-
-
-
diff --git a/resources/view/unauth/register.html b/resources/view/unauth/register.html
index 2ba955045..52a892bd3 100644
--- a/resources/view/unauth/register.html
+++ b/resources/view/unauth/register.html
@@ -77,14 +77,6 @@ Registration
type="button">
-
-
-
-
-
-
diff --git a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java
index 6cffbe175..cc52d8a80 100644
--- a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java
+++ b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java
@@ -7,14 +7,9 @@
import org.springframework.core.io.UrlResource;
import org.springframework.web.multipart.MultipartFile;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.net.MalformedURLException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.nio.file.*;
@UtilityClass
public class FileUtil {
@@ -25,14 +20,13 @@ public static void upload(MultipartFile multipartFile, String directoryPath, Str
throw new IllegalRequestDataException("Select a file to upload.");
}
- File dir = new File(directoryPath);
- if (dir.exists() || dir.mkdirs()) {
- File file = new File(directoryPath + fileName);
- try (OutputStream outStream = new FileOutputStream(file)) {
- outStream.write(multipartFile.getBytes());
- } catch (IOException ex) {
- throw new IllegalRequestDataException("Failed to upload file" + multipartFile.getOriginalFilename());
- }
+ Path dirPath = Paths.get(directoryPath);
+ try {
+ Files.createDirectories(dirPath);
+ Path filePath = dirPath.resolve(fileName);
+ Files.write(filePath, multipartFile.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException ex) {
+ throw new IllegalRequestDataException("Failed to upload file: " + multipartFile.getOriginalFilename());
}
}
@@ -40,22 +34,22 @@ public static Resource download(String fileLink) {
Path path = Paths.get(fileLink);
try {
Resource resource = new UrlResource(path.toUri());
- if (resource.exists() || resource.isReadable()) {
+ if (resource.exists() && resource.isReadable()) {
return resource;
} else {
- throw new IllegalRequestDataException("Failed to download file " + resource.getFilename());
+ throw new IllegalRequestDataException("Failed to download file: " + resource.getFilename());
}
} catch (MalformedURLException ex) {
- throw new NotFoundException("File" + fileLink + " not found");
+ throw new NotFoundException("File not found: " + fileLink);
}
}
public static void delete(String fileLink) {
Path path = Paths.get(fileLink);
try {
- Files.delete(path);
+ Files.deleteIfExists(path);
} catch (IOException ex) {
- throw new IllegalRequestDataException("File" + fileLink + " deletion failed.");
+ throw new IllegalRequestDataException("File deletion failed: " + fileLink);
}
}
diff --git a/src/main/java/com/javarush/jira/bugtracking/task/ActivityService.java b/src/main/java/com/javarush/jira/bugtracking/task/ActivityService.java
index 7938541bb..897b8e5dd 100644
--- a/src/main/java/com/javarush/jira/bugtracking/task/ActivityService.java
+++ b/src/main/java/com/javarush/jira/bugtracking/task/ActivityService.java
@@ -8,6 +8,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.time.Duration;
+import java.time.LocalDateTime;
import java.util.List;
import static com.javarush.jira.bugtracking.task.TaskUtil.getLatestValue;
@@ -73,4 +75,29 @@ private void updateTaskIfRequired(long taskId, String activityStatus, String act
}
}
}
+ public Duration getDurationInProgress(Task task) {
+ LocalDateTime inProgress = null;
+ LocalDateTime readyForReview = null;
+ List activities = handler.getRepository().findAllByTaskIdOrderByUpdatedDesc(task.id());
+ for (Activity activity : activities) {
+ if ("in_progress".equals(activity.getStatusCode())) inProgress=activity.getUpdated();
+ if ("ready_for_review".equals(activity.getStatusCode())) readyForReview=activity.getUpdated();
+ }
+ if (inProgress==null||readyForReview==null) return Duration.ZERO;
+ else return Duration.between(inProgress, readyForReview);
+
+ }
+
+ public Duration getDurationInTesting(Task task) {
+ LocalDateTime done = null;
+ LocalDateTime readyForReview = null;
+ List activities = handler.getRepository().findAllByTaskIdOrderByUpdatedDesc(task.id());
+ for (Activity activity : activities) {
+ if ("done".equals(activity.getStatusCode())) done=activity.getUpdated();
+ if ("ready_for_review".equals(activity.getStatusCode())) readyForReview=activity.getUpdated();
+ }
+ if (done==null||readyForReview==null) return Duration.ZERO;
+ else return Duration.between(readyForReview, done);
+ }
}
+
diff --git a/src/main/java/com/javarush/jira/bugtracking/task/TaskController.java b/src/main/java/com/javarush/jira/bugtracking/task/TaskController.java
index b53f7ff37..4fa2523af 100644
--- a/src/main/java/com/javarush/jira/bugtracking/task/TaskController.java
+++ b/src/main/java/com/javarush/jira/bugtracking/task/TaskController.java
@@ -156,4 +156,10 @@ public TaskTreeNode(TaskTo taskTo) {
this(taskTo, new LinkedList<>());
}
}
+
+ @PostMapping("/{id}/tag")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void addTag(@PathVariable long id, @RequestBody String tag) {
+ taskService.addTag(id, tag);
+ }
}
diff --git a/src/main/java/com/javarush/jira/bugtracking/task/TaskService.java b/src/main/java/com/javarush/jira/bugtracking/task/TaskService.java
index e6f385548..ca9e5155e 100644
--- a/src/main/java/com/javarush/jira/bugtracking/task/TaskService.java
+++ b/src/main/java/com/javarush/jira/bugtracking/task/TaskService.java
@@ -10,6 +10,7 @@
import com.javarush.jira.bugtracking.task.to.TaskToExt;
import com.javarush.jira.bugtracking.task.to.TaskToFull;
import com.javarush.jira.common.error.DataConflictException;
+import com.javarush.jira.common.error.IllegalRequestDataException;
import com.javarush.jira.common.error.NotFoundException;
import com.javarush.jira.common.util.Util;
import com.javarush.jira.login.AuthUser;
@@ -140,4 +141,18 @@ private void checkAssignmentActionPossible(long id, String userType, boolean ass
throw new DataConflictException(String.format(assign ? CANNOT_ASSIGN : CANNOT_UN_ASSIGN, userType, task.getStatusCode()));
}
}
+ @Transactional
+ public void addTag(long taskId, String tag) {
+ if (tag == null || tag.isEmpty()) {
+ throw new IllegalRequestDataException("Tag must not be null or empty");
+ }
+ if (tag.length() > 32) {
+ throw new IllegalRequestDataException("Tag must not exceed 32 characters");
+ }
+
+ Task task = handler.getRepository().getExisted(taskId);
+ task.getTags().add(tag);
+
+ handler.getRepository().saveAndFlush(task);
+ }
}
diff --git a/src/main/java/com/javarush/jira/common/internal/config/H2DataSourceConfig.java b/src/main/java/com/javarush/jira/common/internal/config/H2DataSourceConfig.java
new file mode 100644
index 000000000..03aa6f26a
--- /dev/null
+++ b/src/main/java/com/javarush/jira/common/internal/config/H2DataSourceConfig.java
@@ -0,0 +1,22 @@
+package com.javarush.jira.common.internal.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import javax.sql.DataSource;
+
+@Configuration
+@Profile("nativeTest")
+public class H2DataSourceConfig {
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.h2.Driver");
+ dataSource.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
+ dataSource.setUsername("sa");
+ dataSource.setPassword("");
+ return dataSource;
+ }
+}
diff --git a/src/main/java/com/javarush/jira/common/internal/config/PostgreSqlDataSourceConfig.java b/src/main/java/com/javarush/jira/common/internal/config/PostgreSqlDataSourceConfig.java
new file mode 100644
index 000000000..4733b3a4e
--- /dev/null
+++ b/src/main/java/com/javarush/jira/common/internal/config/PostgreSqlDataSourceConfig.java
@@ -0,0 +1,22 @@
+package com.javarush.jira.common.internal.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import javax.sql.DataSource;
+
+@Configuration
+@Profile("native")
+public class PostgreSqlDataSourceConfig {
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.postgresql.Driver");
+ dataSource.setUrl("jdbc:postgresql://localhost:5432/jira");
+ dataSource.setUsername("jira");
+ dataSource.setPassword("JiraRush");
+ return dataSource;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java b/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java
deleted file mode 100644
index e8e05be05..000000000
--- a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.javarush.jira.login.internal.sociallogin.handler;
-
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-import java.util.Map;
-
-@Component("vk")
-public class VkOAuth2UserDataHandler implements OAuth2UserDataHandler {
- @Override
- public String getFirstName(OAuth2UserData oAuth2UserData) {
- return getAttribute(oAuth2UserData, "first_name");
- }
-
- @Override
- public String getLastName(OAuth2UserData oAuth2UserData) {
- return getAttribute(oAuth2UserData, "last_name");
- }
-
- @Override
- public String getEmail(OAuth2UserData oAuth2UserData) {
- return oAuth2UserData.getData("email");
- }
-
- private String getAttribute(OAuth2UserData oAuth2UserData, String name) {
- List