Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
78eb29e
Removing social network registration buttons and their logic. (VK, Ya…
Aug 7, 2025
14b93b6
Merge pull request #1 from ArtemBurlachkov/TASK_2
ArtemBurlachkov Aug 7, 2025
52e4634
Creat application-sensitive.properties
Aug 8, 2025
2800aa5
Merge pull request #2 from ArtemBurlachkov/TASK_3
ArtemBurlachkov Aug 8, 2025
c64f753
Задача 5 Выполнена. Добавлены тесты для ProfileRestController
Aug 27, 2025
0146b12
Merge pull request #3 from ArtemBurlachkov/TASK_5
ArtemBurlachkov Aug 27, 2025
06bc0bd
Задача 6 Выполнена. Рефакторинг метода загрузки файлов (java.io -> ja…
Aug 28, 2025
c5547ff
Merge pull request #4 from ArtemBurlachkov/TASK_6
ArtemBurlachkov Aug 28, 2025
732cddb
Задача 7 Выполнена. Добавлен функционал добавления тегов к задаче (бе…
Aug 28, 2025
d92a3cf
Merge pull request #5 from ArtemBurlachkov/TASK_7
ArtemBurlachkov Aug 28, 2025
67efe9b
Задача 8 Выполнена. Добавлен подсчет времени сколько задача находилас…
Aug 28, 2025
ea87237
Задача 8 Выполнена. Исправлена логика для прохождения теста. Найдена …
Aug 29, 2025
675f936
Задача 8 Выполнена. Исправлена логика для прохождения теста. Найдена …
Aug 29, 2025
4cc6e53
Update application-sensitive.properties
ArtemBurlachkov Aug 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions resources/static/fontawesome/css/all.css
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -9955,10 +9952,6 @@ readers do not read off random characters that represent icons */
content: "\f3bc";
}

.fa-yandex:before {
content: "\f413";
}

.fa-readme:before {
content: "\f4d5";
}
Expand Down Expand Up @@ -10183,9 +10176,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";
Expand Down
2 changes: 1 addition & 1 deletion resources/static/fontawesome/css/all.min.css

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions resources/view/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ <h3 class="mb-3">Sign in</h3>
type="button">
<i class="fa-brands fa-google"></i>
</a>
<a class="btn btn-primary btn-lg me-2" href="/oauth2/authorization/vk" style="padding-left: 17px; padding-right: 17px;"
type="button">
<i class="fa-brands fa-vk"></i>
</a>
<a class="btn btn-danger btn-lg me-2" href="/oauth2/authorization/yandex" style="padding-left: 21px; padding-right: 21px;"
type="button">
<i class="fa-brands fa-yandex"></i>
</a>
<a class="btn btn-dark btn-lg me-2" href="/oauth2/authorization/github" type="button">
<i class="fa-brands fa-github"></i>
</a>
Expand Down
2 changes: 2 additions & 0 deletions resources/view/task.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ <h5>Status: [[${task.statusCode}]]</h5>
<h5>Type: [[${task.typeCode}]]</h5>
<h5>Updated: [[${#temporals.format(task?.updated, 'MM/dd/yyyy, HH:mm:ss')} ?: '-' ]]</h5>
<h5>Estimate: [[${task.estimate} ?: '-']]</h5>
<h5>TotalTimeInProgress: [[${task.developmentTime} ? (${task.developmentTime} >= 3600 ? ${task.developmentTime/3600} + ' ч ' : '') + (${task.developmentTime % 3600/60} >= 1 ? ${task.developmentTime % 3600/60} + ' мин ' : '') + (${task.developmentTime % 60} + ' сек') : '-']]</h5>
<h5>TotalTimeInTesting: [[${task.testingTime} ? (${task.testingTime} >= 3600 ? ${task.testingTime/3600} + ' ч ' : '') + (${task.testingTime % 3600/60} >= 1 ? ${task.testingTime % 3600/60} + ' мин ' : '') + (${task.testingTime % 60} + ' сек') : '-']]</h5>
<hr>
<h6 th:if="${task.parentId != null}">Parent: <a th:href="@{'/ui/tasks/' + ${task.parentId}}">[[${task.parent.code}]]</a>
</h6>
Expand Down
8 changes: 0 additions & 8 deletions resources/view/unauth/register.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,6 @@ <h3 class="mb-3">Registration</h3>
type="button">
<i class="fa-brands fa-google"></i>
</a>
<a class="btn btn-primary btn-lg me-2" href="/oauth2/authorization/vk" style="padding-left: 17px; padding-right: 17px;"
type="button">
<i class="fa-brands fa-vk"></i>
</a>
<a class="btn btn-danger btn-lg me-2" href="/oauth2/authorization/yandex" style="padding-left: 21px; padding-right: 21px;"
type="button">
<i class="fa-brands fa-yandex"></i>
</a>
<a class="btn btn-dark btn-lg me-2" href="/oauth2/authorization/github" type="button">
<i class="fa-brands fa-github"></i>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ 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());
try {
Path dir = Paths.get(directoryPath); // 1. Создание объекта Path
Files.createDirectories(dir); // 2. Гарантированное создание директорий
Path file = dir.resolve(fileName); // 3. Безопасное формирование пути к файлу
if (Files.exists(file)) {
throw new IllegalRequestDataException("File with name " + fileName + " already exists.");
}
multipartFile.transferTo(file); // 4. Эффективная передача файла
} catch (IOException ex){
throw new IllegalRequestDataException("Failed to upload file" + multipartFile.getOriginalFilename());
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/javarush/jira/bugtracking/task/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class Task extends TitleEntity implements HasCode {
joinColumns = @JoinColumn(name = "task_id"),
uniqueConstraints = @UniqueConstraint(columnNames = {"task_id", "tag"}, name = "uk_task_tag"))
@Column(name = "tag")
@ElementCollection(fetch = FetchType.LAZY)
@ElementCollection(fetch = FetchType.EAGER)
@JoinColumn()
@OnDelete(action = OnDeleteAction.CASCADE)
private Set<@Size(min = 2, max = 32) String> tags = Set.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jakarta.annotation.Nullable;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -23,6 +24,7 @@
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import static com.javarush.jira.common.BaseHandler.createdResponse;

Expand Down Expand Up @@ -156,4 +158,31 @@ public TaskTreeNode(TaskTo taskTo) {
this(taskTo, new LinkedList<>());
}
}

@GetMapping("/{id}/tags")
public Set<String> getTags(@PathVariable long id) {
log.info("get tags for task with id={}", id);
return taskService.getTags(id);
}

@PutMapping("/{id}/tags")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updateTags(@PathVariable long id, @RequestBody Set<@Size(min = 2, max = 32) String> tags) {
log.info("update tags for task with id={}: {}", id, tags);
taskService.updateTags(id, tags);
}

@PostMapping("/{id}/tags")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void addTag(@PathVariable long id, @Size(min = 2, max = 32) @RequestParam String tag) {
log.info("add tag {} to task with id={}", tag, id);
taskService.addTag(id, tag);
}

@DeleteMapping("/{id}/tags/{tag}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void removeTag(@PathVariable long id, @PathVariable String tag) {
log.info("remove tag {} from task with id={}", tag, id);
taskService.removeTag(id, tag);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.javarush.jira.bugtracking.sprint.SprintRepository;
import com.javarush.jira.bugtracking.task.mapper.TaskExtMapper;
import com.javarush.jira.bugtracking.task.mapper.TaskFullMapper;
import com.javarush.jira.bugtracking.task.to.ActivityTo;
import com.javarush.jira.bugtracking.task.to.TaskToExt;
import com.javarush.jira.bugtracking.task.to.TaskToFull;
import com.javarush.jira.common.error.DataConflictException;
Expand All @@ -18,9 +19,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.time.temporal.ChronoUnit;
import java.util.*;

import static com.javarush.jira.bugtracking.ObjectType.TASK;
import static com.javarush.jira.bugtracking.task.TaskUtil.fillExtraFields;
Expand Down Expand Up @@ -92,6 +95,8 @@ public TaskToFull get(long id) {
List<Activity> activities = activityHandler.getRepository().findAllByTaskIdOrderByUpdatedDesc(id);
fillExtraFields(taskToFull, activities);
taskToFull.setActivityTos(activityHandler.getMapper().toToList(activities));
taskToFull.setDevelopmentTime(calculateTimeSpentInStatus(taskToFull, "in_progress"));
taskToFull.setTestingTime(calculateTimeSpentInStatus(taskToFull, "test"));
return taskToFull;
}

Expand Down Expand Up @@ -140,4 +145,79 @@ private void checkAssignmentActionPossible(long id, String userType, boolean ass
throw new DataConflictException(String.format(assign ? CANNOT_ASSIGN : CANNOT_UN_ASSIGN, userType, task.getStatusCode()));
}
}

@Transactional(readOnly = true)
public Set<String> getTags(long taskId) {
Task task = handler.getRepository().getExisted(taskId);
return new HashSet<>(task.getTags());
}

@Transactional
public void updateTags(long taskId, Set<String> tags) {
Assert.notNull(tags, "tags must not be null");
Task task = handler.getRepository().getExisted(taskId);
task.setTags(tags);
}

@Transactional
public void addTag(long taskId, String tag) {
Assert.notNull(tag, "tag must not be null");
Task task = handler.getRepository().getExisted(taskId);
Set<String> tags = new HashSet<>(task.getTags());
if (tags.add(tag)) {
task.setTags(tags);
}
}

@Transactional
public void removeTag(long taskId, String tag) {
Task task = handler.getRepository().getExisted(taskId);
Set<String> tags = new HashSet<>(task.getTags());
if (tags.remove(tag)) {
task.setTags(tags);
}
}
public long calculateTimeSpentInStatus(TaskToFull taskToFull, String targetStatus) {
List<ActivityTo> activities = taskToFull.getActivityTos().stream()
.sorted((o1, o2) -> {
if (o1.getUpdated() == null || o2.getUpdated() == null) {
return 0;
}
return o1.getUpdated().compareTo(o2.getUpdated());
})
.toList();

if (CollectionUtils.isEmpty(activities)) {
return 0;
}

long result = 0;
for (int i = 0; i < activities.size(); i++) {
ActivityTo activityStart = activities.get(i);
boolean activityStartHasEnd = false;
if (!Objects.equals(activityStart.getStatusCode(), targetStatus) || activityStart.getUpdated() == null) {
continue;
}

ActivityTo activityEnd = null;
for (int j = i + 1; j < activities.size(); j++) {
activityEnd = activities.get(j);
if (!Objects.equals(activityEnd.getStatusCode(), targetStatus) && activityEnd.getUpdated() != null) {
activityStartHasEnd = true;
i = j;
break;
}
}

LocalDateTime start = activityStart.getUpdated();
if (activityStartHasEnd) {
LocalDateTime end = activityEnd.getUpdated();
result += (start.until(end, ChronoUnit.SECONDS));
} else {
result += (start.until(LocalDateTime.now(), ChronoUnit.SECONDS));
}
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,24 @@ public class TaskToFull extends TaskToExt {
CodeTo sprint;
@Setter
List<ActivityTo> activityTos;
@Setter
long developmentTime = 0;
@Setter
long testingTime = 0;

public TaskToFull(Long id, String code, String title, String description, String typeCode, String statusCode, String priorityCode,
LocalDateTime updated, Integer estimate, CodeTo parent, CodeTo project, CodeTo sprint, List<ActivityTo> activityTos) {
public TaskToFull(Long id,
String code,
String title,
String description,
String typeCode,
String statusCode,
String priorityCode,
LocalDateTime updated,
Integer estimate,
CodeTo parent,
CodeTo project,
CodeTo sprint,
List<ActivityTo> activityTos) {
super(id, code, title, description, typeCode, statusCode, priorityCode, updated, estimate,
parent == null ? null : parent.getId(), project.getId(), sprint == null ? null : sprint.getId());
this.parent = parent;
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public MailCase(String email, String name, String template, String result) {
this.name = name;
this.template = template;
this.result = result;
this.dateTime = LocalDateTime.now();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public void update(ProfileTo profileTo, long id) {
ValidationUtil.assureIdConsistent(profileTo, id);
ValidationUtil.assureIdConsistent(profileTo.getContacts(), id);
ProfileUtil.checkContactsExist(profileTo.getContacts());
Profile profile = profileMapper.updateFromTo(profileRepository.getOrCreate(profileTo.id()), profileTo);

Profile orCreate = profileRepository.getOrCreate(profileTo.id());
Profile profile = profileMapper.updateFromTo(orCreate, profileTo);
profileRepository.save(profile);
}
}
19 changes: 19 additions & 0 deletions src/main/resources/application-sensitive.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Database settings
spring.datasource_db.url=jdbc:postgresql://localhost:5432/jira
spring.datasource_db.username=jira
spring.datasource_db.password=JiraRush

# OAuth settings
#-GITHUB-
oauth.github.client-id=3d0d8738e65881fff266
oauth.github.client-secret=0f97031ce6178b7dfb67a6af587f37e222a16120
#-GOOGLE-
oauth.googlec.client-id=329113642700-f8if6pu68j2repq3ef6umd5jgiliup60.apps.googleusercontent.com
oauth.googlec.client-secret=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
#-GITLAB-
oauth.gitlab.client-id=b8520a3266089063c0d8261cce36971defa513f5ffd9f9b7a3d16728fc83a494
oauth.gitlab.client-secret=e72c65320cf9d6495984a37b0f9cc03ec46be0bb6f071feaebbfe75168117004

# Mail server settings
mail.server.username=добавить почту
mail.server.password=добавить пароль для приложения
Loading