Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ target
logs
attachments
*.patch
.env


14 changes: 14 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

services:
postgres-db:
image: postgres
container_name: postgres-db
ports:
- "5432:5432"
environment:
POSTGRES_USER: jira
POSTGRES_PASSWORD: JiraRush
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./pgdata:/var/lib/postgresql/data
restart: unless-stopped
2 changes: 1 addition & 1 deletion config/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# https://losst.ru/ustanovka-nginx-ubuntu-16-04

# https://pai-bx.com/wiki/nginx/2332-useful-redirects-in-nginx/#1
# sudo iptables -A INPUT ! -s 127.0.0.1 -p tcp -m tcp --dport 8080 -j DROP
server {
Expand Down
30 changes: 30 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>1.18.30</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
Expand Down Expand Up @@ -142,10 +145,37 @@
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>4.0.0</version>
</dependency>

</dependencies>

<build>
<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.0.Beta1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand Down
12 changes: 3 additions & 9 deletions resources/static/fontawesome/css/all.css
Original file line number Diff line number Diff line change
Expand Up @@ -8603,9 +8603,7 @@ 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,9 +9953,7 @@ 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 +10179,7 @@ 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
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
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 @@ -2,6 +2,7 @@

import com.javarush.jira.bugtracking.ObjectType;
import com.javarush.jira.login.AuthUser;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
Expand All @@ -27,6 +28,10 @@ public class AttachmentController {

@Transactional
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(
summary = "Завантажити файл вкладення",
description = "Додає нове вкладення до об'єкта певного типу (наприклад, задачі чи проєкту) та зберігає його у файловій системі"
)
public ResponseEntity<Attachment> upload(@RequestPart MultipartFile file, @RequestParam ObjectType type,
@RequestParam Long objectId, @AuthenticationPrincipal AuthUser authUser) {
log.debug("upload file {} to folder {}", file.getOriginalFilename(), type.toString().toLowerCase());
Expand All @@ -41,6 +46,10 @@ public ResponseEntity<Attachment> upload(@RequestPart MultipartFile file, @Reque

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Видалити файл вкладення",
description = "Видаляє вкладення з бази даних та фізично з файлової системи за ID"
)
public void delete(@PathVariable Long id) {
log.debug("delete file id = {}", id);
Attachment attachment = repository.getExisted(id);
Expand All @@ -49,6 +58,10 @@ public void delete(@PathVariable Long id) {
}

@GetMapping(value = "/download/{id}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@Operation(
summary = "Завантажити файл вкладення",
description = "Повертає файл вкладення як ресурс для завантаження за його ID"
)
public ResponseEntity<Resource> download(@PathVariable long id) {
log.debug("download file id = {}", id);
Attachment attachment = repository.getExisted(id);
Expand All @@ -59,12 +72,20 @@ public ResponseEntity<Resource> download(@PathVariable long id) {
}

@GetMapping("/for-object")
@Operation(
summary = "Отримати всі вкладення для об'єкта",
description = "Повертає список усіх вкладень, прикріплених до вказаного об'єкта певного типу (за objectId та типом)"
)
public List<Attachment> getAllForObject(@RequestParam long objectId, @RequestParam ObjectType type) {
log.info("get all attachment by objectId = {}", objectId);
return repository.getAllForObject(objectId, type);
}

@GetMapping
@Operation(
summary = "Отримати всі вкладення",
description = "Повертає повний список всіх вкладень, збережених у системі"
)
public List<Attachment> getAll() {
log.info("get all attachment");
return repository.findAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ 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 dirPath = Paths.get(directoryPath);
Files.createDirectories(dirPath);

Path filePath = dirPath.resolve(fileName);
Files.write(filePath, multipartFile.getBytes());

} catch (IOException ex) {
throw new IllegalRequestDataException("Failed to upload file " + multipartFile.getOriginalFilename());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.javarush.jira.bugtracking.Handlers;
import com.javarush.jira.bugtracking.project.to.ProjectTo;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -22,29 +23,50 @@ public class ProjectController {
private final Handlers.ProjectHandler handler;

@GetMapping("/projects")
@Operation(
summary = "Отримати список усіх проєктів",
description = "Повертає список усіх проєктів у порядку від найновіших до найстаріших"
)

public List<ProjectTo> getAll() {
return handler.getAllTos(ProjectRepository.NEWEST_FIRST);
}

@GetMapping("/projects/{id}")
@Operation(
summary = "Отримати проєкт за ID",
description = "Повертає повну інформацію про проєкт із зазначеним ID"
)
public ProjectTo getById(@PathVariable Long id) {
return handler.getTo(id);
}

@PostMapping(path = "/mngr/projects", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(
summary = "Створити новий проєкт",
description = "Створює новий проєкт на основі переданих даних"
)
public ResponseEntity<Project> create(@Valid @RequestBody ProjectTo projectTo) {
Project created = handler.createWithBelong(projectTo, PROJECT, "project_author");
return createdResponse(REST_URL + "/projects", created);
}

@PutMapping("/mngr/projects/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Оновити проєкт",
description = "Оновлює існуючий проєкт за його ID"
)
public void update(@Valid @RequestBody ProjectTo projectTo, @PathVariable Long id) {
handler.updateFromTo(projectTo, id);
}

@PatchMapping("/mngr/projects/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Увімкнути/вимкнути проєкт",
description = "Змінює статус активності проєкту: enabled = true/false"
)
public void enable(@PathVariable long id, @RequestParam boolean enabled) {
handler.enable(id, enabled);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.javarush.jira.bugtracking.report;

import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
Expand All @@ -20,6 +21,10 @@ public class ReportRestController {
private final ReportRepository reportRepository;

@GetMapping
@Operation(
summary = "Отримати звіт по задачах спринту",
description = "Повертає список зведеної інформації про задачі у межах вказаного спринту, включаючи кількість, статуси та інші ключові метрики"
)
public List<TaskSummary> getTaskSummaries(@PathVariable long sprintId) {
log.info("get task summaries for sprint with id={}", sprintId);
return reportRepository.getTaskSummaries(sprintId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.javarush.jira.bugtracking.Handlers;
import com.javarush.jira.bugtracking.project.ProjectRepository;
import com.javarush.jira.bugtracking.sprint.to.SprintTo;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Nullable;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
Expand Down Expand Up @@ -32,11 +33,19 @@ public class SprintController {
private final Handlers.SprintHandler handler;

@GetMapping("/sprints/{id}")
@Operation(
summary = "Отримати спринт за ID",
description = "Повертає повну інформацію про спринт із зазначеним ID"
)
public SprintTo get(@PathVariable long id) {
return handler.getTo(id);
}

@GetMapping("/sprints/by-project")
@Operation(
summary = "Отримати спринти за проєктом",
description = "Повертає список усіх спринтів для проєкту. Якщо enabled=true, то тільки активні"
)
public List<SprintTo> getAllByProject(@RequestParam long projectId, @RequestParam @Nullable Boolean enabled) {
log.info("get all for project {} and enabled {}", projectId, enabled);
checkProjectExists(projectId);
Expand All @@ -52,6 +61,10 @@ private void checkProjectExists(long id) {
}

@GetMapping("/sprints/by-project-and-status")
@Operation(
summary = "Отримати спринти за статусом",
description = "Повертає спринти проєкту з певним статусом (наприклад: active, done)"
)
public List<Sprint> getAllByProjectAndStatus(@RequestParam long projectId, @NotBlank @RequestParam String statusCode) {
log.info("get all {} sprints for project with id={}", statusCode, projectId);
checkProjectExists(projectId);
Expand All @@ -60,20 +73,32 @@ public List<Sprint> getAllByProjectAndStatus(@RequestParam long projectId, @NotB

@PostMapping(path = "/mngr/sprints", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.CREATED)
@Operation(
summary = "Створити спринт",
description = "Створює новий спринт у вказаному проєкті"
)
public ResponseEntity<Sprint> createWithLocation(@Valid @RequestBody SprintTo sprintTo) {
Sprint created = handler.createWithBelong(sprintTo, SPRINT, "sprint_author");
return createdResponse(REST_URL + "/sprints", created);
}

@PutMapping(path = "/mngr/sprints/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Оновити спринт",
description = "Оновлює існуючий спринт за його ID"
)
public void update(@Validated @RequestBody SprintTo sprintTo, @PathVariable long id) {
handler.updateFromTo(sprintTo, id);
}

@Transactional
@PatchMapping("/mngr/sprints/{id}/change-status")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Змінити статус спринта",
description = "Змінює статус спринта (наприклад, з active на done)"
)
public void changeStatusCode(@PathVariable long id, @RequestParam String statusCode) {
log.info("change statusCode of sprint {}", id);
Sprint sprint = handler.getRepository().getExisted(id);
Expand All @@ -82,6 +107,10 @@ public void changeStatusCode(@PathVariable long id, @RequestParam String statusC

@PatchMapping("/mngr/sprints/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Активувати/деактивувати спринт",
description = "Вмикає або вимикає спринт (enabled = true/false)"
)
public void enable(@PathVariable long id, @RequestParam boolean enabled) {
handler.enable(id, enabled);
}
Expand Down
Loading